From ba518aa110d2fe84f6163c785da9fc7ab4ea9eae Mon Sep 17 00:00:00 2001 From: Hisham Muhammad Date: Thu, 22 Aug 2024 17:49:09 -0300 Subject: Teal: add generated modules --- src/luarocks/admin/cache.lua | 89 ++++ src/luarocks/admin/cmd/add.lua | 135 +++++ src/luarocks/admin/cmd/make_manifest.lua | 55 ++ src/luarocks/admin/cmd/refresh_cache.lua | 36 ++ src/luarocks/admin/cmd/remove.lua | 96 ++++ src/luarocks/admin/index.lua | 190 +++++++ src/luarocks/build.lua | 487 +++++++++++++++++ src/luarocks/build/builtin.lua | 403 ++++++++++++++ src/luarocks/build/cmake.lua | 91 ++++ src/luarocks/build/command.lua | 51 ++ src/luarocks/build/make.lua | 107 ++++ src/luarocks/cmd.lua | 807 ++++++++++++++++++++++++++++ src/luarocks/cmd/build.lua | 211 ++++++++ src/luarocks/cmd/config.lua | 402 ++++++++++++++ src/luarocks/cmd/doc.lua | 158 ++++++ src/luarocks/cmd/download.lua | 61 +++ src/luarocks/cmd/init.lua | 230 ++++++++ src/luarocks/cmd/install.lua | 259 +++++++++ src/luarocks/cmd/lint.lua | 59 +++ src/luarocks/cmd/list.lua | 113 ++++ src/luarocks/cmd/make.lua | 179 +++++++ src/luarocks/cmd/new_version.lua | 237 +++++++++ src/luarocks/cmd/pack.lua | 41 ++ src/luarocks/cmd/path.lua | 88 ++++ src/luarocks/cmd/purge.lua | 79 +++ src/luarocks/cmd/remove.lua | 77 +++ src/luarocks/cmd/search.lua | 91 ++++ src/luarocks/cmd/show.lua | 341 ++++++++++++ src/luarocks/cmd/test.lua | 53 ++ src/luarocks/cmd/unpack.lua | 172 ++++++ src/luarocks/cmd/upload.lua | 144 +++++ src/luarocks/cmd/which.lua | 44 ++ src/luarocks/cmd/write_rockspec.lua | 423 +++++++++++++++ src/luarocks/config.lua | 38 ++ src/luarocks/core/dir.lua | 95 ++++ src/luarocks/core/manif.lua | 124 +++++ src/luarocks/core/path.lua | 151 ++++++ src/luarocks/core/persist.lua | 70 +++ src/luarocks/core/sysdetect.lua | 508 ++++++++++++++++++ src/luarocks/core/types/query.lua | 13 + src/luarocks/core/types/result.lua | 14 + src/luarocks/core/types/rockspec.lua | 78 +++ src/luarocks/core/util.lua | 334 ++++++++++++ src/luarocks/core/vers.lua | 211 ++++++++ src/luarocks/deplocks.lua | 111 ++++ src/luarocks/deps.lua | 877 +++++++++++++++++++++++++++++++ src/luarocks/dir.lua | 65 +++ src/luarocks/download.lua | 76 +++ src/luarocks/fetch.lua | 615 ++++++++++++++++++++++ src/luarocks/fetch/cvs.lua | 53 ++ src/luarocks/fetch/git.lua | 166 ++++++ src/luarocks/fetch/git_file.lua | 22 + src/luarocks/fetch/git_http.lua | 29 + src/luarocks/fetch/git_https.lua | 7 + src/luarocks/fetch/git_ssh.lua | 35 ++ src/luarocks/fetch/hg.lua | 64 +++ src/luarocks/fetch/hg_http.lua | 27 + src/luarocks/fetch/hg_https.lua | 8 + src/luarocks/fetch/hg_ssh.lua | 8 + src/luarocks/fetch/sscm.lua | 45 ++ src/luarocks/fetch/svn.lua | 63 +++ src/luarocks/fun.lua | 138 +++++ src/luarocks/loader.lua | 333 ++++++++++++ src/luarocks/manif.lua | 229 ++++++++ src/luarocks/manif/writer.lua | 459 ++++++++++++++++ src/luarocks/pack.lua | 188 +++++++ src/luarocks/path.lua | 254 +++++++++ src/luarocks/persist.lua | 275 ++++++++++ src/luarocks/queries.lua | 211 ++++++++ src/luarocks/remove.lua | 140 +++++ src/luarocks/repo_writer.lua | 52 ++ src/luarocks/repos.lua | 690 ++++++++++++++++++++++++ src/luarocks/require.lua | 2 + src/luarocks/results.lua | 60 +++ src/luarocks/rockspecs.lua | 184 +++++++ src/luarocks/search.lua | 385 ++++++++++++++ src/luarocks/signing.lua | 48 ++ src/luarocks/test.lua | 110 ++++ src/luarocks/test/busted.lua | 55 ++ src/luarocks/test/command.lua | 55 ++ src/luarocks/tools/patch.lua | 746 ++++++++++++++++++++++++++ src/luarocks/tools/tar.lua | 210 ++++++++ src/luarocks/tools/zip.lua | 575 ++++++++++++++++++++ src/luarocks/type/manifest.lua | 92 ++++ src/luarocks/type/rockspec.lua | 261 +++++++++ src/luarocks/type_check.lua | 237 +++++++++ src/luarocks/upload/api.lua | 300 +++++++++++ src/luarocks/upload/multipart.lua | 117 +++++ src/luarocks/util.lua | 611 +++++++++++++++++++++ 89 files changed, 16933 insertions(+) create mode 100644 src/luarocks/admin/cache.lua create mode 100644 src/luarocks/admin/cmd/add.lua create mode 100644 src/luarocks/admin/cmd/make_manifest.lua create mode 100644 src/luarocks/admin/cmd/refresh_cache.lua create mode 100644 src/luarocks/admin/cmd/remove.lua create mode 100644 src/luarocks/admin/index.lua create mode 100644 src/luarocks/build.lua create mode 100644 src/luarocks/build/builtin.lua create mode 100644 src/luarocks/build/cmake.lua create mode 100644 src/luarocks/build/command.lua create mode 100644 src/luarocks/build/make.lua create mode 100644 src/luarocks/cmd.lua create mode 100644 src/luarocks/cmd/build.lua create mode 100644 src/luarocks/cmd/config.lua create mode 100644 src/luarocks/cmd/doc.lua create mode 100644 src/luarocks/cmd/download.lua create mode 100644 src/luarocks/cmd/init.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.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/test.lua create mode 100644 src/luarocks/cmd/unpack.lua create mode 100644 src/luarocks/cmd/upload.lua create mode 100644 src/luarocks/cmd/which.lua create mode 100644 src/luarocks/cmd/write_rockspec.lua create mode 100644 src/luarocks/config.lua create mode 100644 src/luarocks/core/dir.lua create mode 100644 src/luarocks/core/manif.lua create mode 100644 src/luarocks/core/path.lua create mode 100644 src/luarocks/core/persist.lua create mode 100644 src/luarocks/core/sysdetect.lua create mode 100644 src/luarocks/core/types/query.lua create mode 100644 src/luarocks/core/types/result.lua create mode 100644 src/luarocks/core/types/rockspec.lua create mode 100644 src/luarocks/core/util.lua create mode 100644 src/luarocks/core/vers.lua create mode 100644 src/luarocks/deplocks.lua create mode 100644 src/luarocks/deps.lua create mode 100644 src/luarocks/dir.lua create mode 100644 src/luarocks/download.lua create mode 100644 src/luarocks/fetch.lua create mode 100644 src/luarocks/fetch/cvs.lua create mode 100644 src/luarocks/fetch/git.lua create mode 100644 src/luarocks/fetch/git_file.lua create mode 100644 src/luarocks/fetch/git_http.lua create mode 100644 src/luarocks/fetch/git_https.lua create mode 100644 src/luarocks/fetch/git_ssh.lua create mode 100644 src/luarocks/fetch/hg.lua create mode 100644 src/luarocks/fetch/hg_http.lua create mode 100644 src/luarocks/fetch/hg_https.lua create mode 100644 src/luarocks/fetch/hg_ssh.lua create mode 100644 src/luarocks/fetch/sscm.lua create mode 100644 src/luarocks/fetch/svn.lua create mode 100644 src/luarocks/fun.lua create mode 100644 src/luarocks/loader.lua create mode 100644 src/luarocks/manif.lua create mode 100644 src/luarocks/manif/writer.lua create mode 100644 src/luarocks/pack.lua create mode 100644 src/luarocks/path.lua create mode 100644 src/luarocks/persist.lua create mode 100644 src/luarocks/queries.lua create mode 100644 src/luarocks/remove.lua create mode 100644 src/luarocks/repo_writer.lua create mode 100644 src/luarocks/repos.lua create mode 100644 src/luarocks/require.lua create mode 100644 src/luarocks/results.lua create mode 100644 src/luarocks/rockspecs.lua create mode 100644 src/luarocks/search.lua create mode 100644 src/luarocks/signing.lua create mode 100644 src/luarocks/test.lua create mode 100644 src/luarocks/test/busted.lua create mode 100644 src/luarocks/test/command.lua create mode 100644 src/luarocks/tools/patch.lua create mode 100644 src/luarocks/tools/tar.lua create mode 100644 src/luarocks/tools/zip.lua create mode 100644 src/luarocks/type/manifest.lua create mode 100644 src/luarocks/type/rockspec.lua create mode 100644 src/luarocks/type_check.lua create mode 100644 src/luarocks/upload/api.lua create mode 100644 src/luarocks/upload/multipart.lua create mode 100644 src/luarocks/util.lua (limited to 'src') diff --git a/src/luarocks/admin/cache.lua b/src/luarocks/admin/cache.lua new file mode 100644 index 00000000..11bcc818 --- /dev/null +++ b/src/luarocks/admin/cache.lua @@ -0,0 +1,89 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local os = _tl_compat and _tl_compat.os or os; local string = _tl_compat and _tl_compat.string or string + + +local cache = {} + + +local fs = require("luarocks.fs") +local cfg = require("luarocks.core.cfg") +local dir = require("luarocks.dir") +local util = require("luarocks.util") + +function cache.get_upload_server(server) + if not server then server = cfg.upload_server end + if not server then + return nil, nil, "No server specified and no default configured with upload_server." + end + return server, cfg.upload_servers and cfg.upload_servers[server] +end + +function cache.get_server_urls(server, upload_server) + local download_url = server + local login_url = nil + if upload_server then + if upload_server.rsync then download_url = "rsync://" .. upload_server.rsync + elseif upload_server.http then download_url = "http://" .. upload_server.http + elseif upload_server.ftp then download_url = "ftp://" .. upload_server.ftp + end + + if upload_server.ftp then login_url = "ftp://" .. upload_server.ftp + elseif upload_server.sftp then login_url = "sftp://" .. upload_server.sftp + end + end + return download_url, login_url +end + +function cache.split_server_url(url, user, password) + local protocol, server_path = dir.split_url(url) + if protocol == "file" then + server_path = fs.absolute_name(server_path) + elseif server_path:match("@") then + local credentials + credentials, server_path = server_path:match("([^@]*)@(.*)") + if credentials:match(":") then + user, password = credentials:match("([^:]*):(.*)") + else + user = credentials + end + end + local local_cache = dir.path(cfg.local_cache, (server_path:gsub("[\\/]", "_"))) + return local_cache, protocol, server_path, user, password +end + +local function download_cache(protocol, server_path, user, password) + os.remove("index.html") + + if protocol == "rsync" then + local srv, path = server_path:match("([^/]+)(/.+)") + return fs.execute(cfg.variables.RSYNC .. " " .. cfg.variables.RSYNCFLAGS .. " -e ssh " .. user .. "@" .. srv .. ":" .. path .. "/ ./") + elseif protocol == "file" then + return fs.copy_contents(server_path, ".") + else + local login_info = "" + if user then login_info = " --user=" .. user end + if password then login_info = login_info .. " --password=" .. password end + return fs.execute(cfg.variables.WGET .. " --no-cache -q -m -np -nd " .. protocol .. "://" .. server_path .. login_info) + end +end + +function cache.refresh_local_cache(url, given_user, given_password) + local local_cache, protocol, server_path, user, password = cache.split_server_url(url, given_user, given_password) + + local ok, err = fs.make_dir(local_cache) + if not ok then + return nil, "Failed creating local cache dir: " .. err + end + + fs.change_dir(local_cache) + + util.printout("Refreshing cache " .. local_cache .. "...") + + ok = download_cache(protocol, server_path, user, password) + if not ok then + return nil, "Failed downloading cache." + end + + return local_cache, protocol, server_path, user, password +end + +return cache diff --git a/src/luarocks/admin/cmd/add.lua b/src/luarocks/admin/cmd/add.lua new file mode 100644 index 00000000..862a08a8 --- /dev/null +++ b/src/luarocks/admin/cmd/add.lua @@ -0,0 +1,135 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table + + +local add = {} + + +local cfg = require("luarocks.core.cfg") +local util = require("luarocks.util") +local dir = require("luarocks.dir") +local writer = require("luarocks.manif.writer") +local fs = require("luarocks.fs") +local cache = require("luarocks.admin.cache") +local index = require("luarocks.admin.index") + + + + + +function add.add_to_parser(parser) + local cmd = parser:command("add", "Add a rock or rockspec to a rocks server.", util.see_also()) + + cmd:argument("rocks", "A local rockspec or rock file."): + args("+") + + cmd:option("--server", "The server to use. If not given, the default server " .. + "set in the upload_server variable from the configuration file is used instead."): + target("add_server") + cmd:flag("--no-refresh", "Do not refresh the local cache prior to " .. + "generation of the updated manifest.") + cmd:flag("--index", "Produce an index.html file for the manifest. This " .. + "flag is automatically set if an index.html file already exists.") +end + +local function zip_manifests() + for ver in util.lua_versions() do + local file = "manifest-" .. ver + local zip = file .. ".zip" + fs.delete(dir.path(fs.current_dir(), zip)) + fs.zip(zip, file) + end +end + +local function add_files_to_server(refresh, rockfiles, server, upload_server, do_index) + + local download_url, login_url = cache.get_server_urls(server, upload_server) + local at = fs.current_dir() + local refresh_fn = refresh and cache.refresh_local_cache or cache.split_server_url + + local local_cache, protocol, server_path, user, password = refresh_fn(download_url, cfg.upload_user, cfg.upload_password) + if not local_cache then + return nil, protocol + end + + if not login_url then + login_url = protocol .. "://" .. server_path + end + + local ok, err = fs.change_dir(at) + if not ok then return nil, err end + + local files = {} + for _, rockfile in ipairs(rockfiles) do + if fs.exists(rockfile) then + util.printout("Copying file " .. rockfile .. " to " .. local_cache .. "...") + local absolute = fs.absolute_name(rockfile) + fs.copy(absolute, local_cache, "read") + table.insert(files, dir.base_name(absolute)) + else + util.printerr("File " .. rockfile .. " not found") + end + end + if #files == 0 then + return nil, "No files found" + end + + local ok, err = fs.change_dir(local_cache) + if not ok then return nil, err end + + util.printout("Updating manifest...") + writer.make_manifest(local_cache, "one", true) + + zip_manifests() + + if fs.exists("index.html") then + do_index = true + end + + if do_index then + util.printout("Updating index.html...") + index.make_index(local_cache) + end + + local login_info = "" + if user then login_info = " -u " .. user end + if password then login_info = login_info .. ":" .. password end + if not login_url:match("/$") then + login_url = login_url .. "/" + end + + if do_index then + table.insert(files, "index.html") + end + table.insert(files, "manifest") + for ver in util.lua_versions() do + table.insert(files, "manifest-" .. ver) + table.insert(files, "manifest-" .. ver .. ".zip") + end + + + + local cmd + if protocol == "rsync" then + local srv, path = server_path:match("([^/]+)(/.+)") + cmd = cfg.variables.RSYNC .. " " .. cfg.variables.RSYNCFLAGS .. " -e ssh " .. local_cache .. "/ " .. user .. "@" .. srv .. ":" .. path .. "/" + elseif protocol == "file" then + return fs.copy_contents(local_cache, server_path) + elseif upload_server and upload_server.sftp then + local part1, part2 = upload_server.sftp:match("^([^/]*)/(.*)$") + cmd = cfg.variables.SCP .. " " .. table.concat(files, " ") .. " " .. user .. "@" .. part1 .. ":/" .. part2 + else + cmd = cfg.variables.CURL .. " " .. login_info .. " -T '{" .. table.concat(files, ",") .. "}' " .. login_url + end + + util.printout(cmd) + return fs.execute(cmd) +end + +function add.command(args) + local server, server_table, err = cache.get_upload_server(args.add_server or args.server) + if not server then return nil, err end + return add_files_to_server(not args.no_refresh, args.rocks, server, server_table, args.index) +end + + +return add diff --git a/src/luarocks/admin/cmd/make_manifest.lua b/src/luarocks/admin/cmd/make_manifest.lua new file mode 100644 index 00000000..b028f900 --- /dev/null +++ b/src/luarocks/admin/cmd/make_manifest.lua @@ -0,0 +1,55 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local string = _tl_compat and _tl_compat.string or string + + +local make_manifest = {} + + +local writer = require("luarocks.manif.writer") +local index = require("luarocks.admin.index") +local cfg = require("luarocks.core.cfg") +local util = require("luarocks.util") +local deps = require("luarocks.deps") +local fs = require("luarocks.fs") +local dir = require("luarocks.dir") + + + + + +function make_manifest.add_to_parser(parser) + local cmd = parser:command("make_manifest", "Compile a manifest file for a repository.", util.see_also()) + + cmd:argument("repository", "Local repository pathname."): + args("?") + + cmd:flag("--local-tree", "If given, do not write versioned versions of the manifest file.\n" .. + "Use this when rebuilding the manifest of a local rocks tree.") + util.deps_mode_option(cmd) +end + + + + +function make_manifest.command(args) + local repo = args.repository or cfg.rocks_dir + + util.printout("Making manifest for " .. repo) + + if repo:match("/lib/luarocks") and not args.local_tree then + util.warning("This looks like a local rocks tree, but you did not pass --local-tree.") + end + + local ok, err = writer.make_manifest(repo, deps.get_deps_mode(args), not args.local_tree) + if ok and not args.local_tree then + util.printout("Generating index.html for " .. repo) + index.make_index(repo) + end + if args.local_tree then + for luaver in util.lua_versions() do + fs.delete(dir.path(repo, "manifest-" .. luaver)) + end + end + return ok, err +end + +return make_manifest diff --git a/src/luarocks/admin/cmd/refresh_cache.lua b/src/luarocks/admin/cmd/refresh_cache.lua new file mode 100644 index 00000000..08e90bbc --- /dev/null +++ b/src/luarocks/admin/cmd/refresh_cache.lua @@ -0,0 +1,36 @@ + + +local refresh_cache = {} + + +local cfg = require("luarocks.core.cfg") +local util = require("luarocks.util") +local cache = require("luarocks.admin.cache") + + + + + +function refresh_cache.add_to_parser(parser) + local cmd = parser:command("refresh_cache", "Refresh local cache of a remote rocks server.", util.see_also()) + + cmd:option("--from", "The server to use. If not given, the default server " .. + "set in the upload_server variable from the configuration file is used instead."): + argname("") +end + +function refresh_cache.command(args) + local server, upload_server, err = cache.get_upload_server(args.server) + if not server then return nil, err end + local download_url = cache.get_server_urls(server, upload_server) + + local ok, err = cache.refresh_local_cache(download_url, cfg.upload_user, cfg.upload_password) + if not ok then + return nil, err + else + return true + end +end + + +return refresh_cache diff --git a/src/luarocks/admin/cmd/remove.lua b/src/luarocks/admin/cmd/remove.lua new file mode 100644 index 00000000..eee761fa --- /dev/null +++ b/src/luarocks/admin/cmd/remove.lua @@ -0,0 +1,96 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local string = _tl_compat and _tl_compat.string or string + + +local admin_remove = {} + + +local cfg = require("luarocks.core.cfg") +local util = require("luarocks.util") +local dir = require("luarocks.dir") +local writer = require("luarocks.manif.writer") +local fs = require("luarocks.fs") +local cache = require("luarocks.admin.cache") +local index = require("luarocks.admin.index") + + + + + +function admin_remove.add_to_parser(parser) + local cmd = parser:command("remove", "Remove a rock or rockspec from a rocks server.", util.see_also()) + + cmd:argument("rocks", "A local rockspec or rock file."): + args("+") + + cmd:option("--server", "The server to use. If not given, the default server " .. + "set in the upload_server variable from the configuration file is used instead.") + cmd:flag("--no-refresh", "Do not refresh the local cache prior to " .. + "generation of the updated manifest.") +end + +local function remove_files_from_server(refresh, rockfiles, server, upload_server) + + local download_url, login_url = cache.get_server_urls(server, upload_server) + local at = fs.current_dir() + local refresh_fn = refresh and cache.refresh_local_cache or cache.split_server_url + + local local_cache, protocol, server_path, user, password = refresh_fn(download_url, cfg.upload_user, cfg.upload_password) + if not local_cache then + return nil, protocol + end + + local ok, err = fs.change_dir(at) + if not ok then return nil, err end + + local nr_files = 0 + for _, rockfile in ipairs(rockfiles) do + local basename = dir.base_name(rockfile) + local file = dir.path(local_cache, basename) + util.printout("Removing file " .. file .. "...") + fs.delete(file) + if not fs.exists(file) then + nr_files = nr_files + 1 + else + util.printerr("Failed removing " .. file) + end + end + if nr_files == 0 then + return nil, "No files removed." + end + + local ok, err = fs.change_dir(local_cache) + if not ok then return nil, err end + + util.printout("Updating manifest...") + writer.make_manifest(local_cache, "one", true) + util.printout("Updating index.html...") + index.make_index(local_cache) + + if protocol == "file" then + local cmd = cfg.variables.RSYNC .. " " .. cfg.variables.RSYNCFLAGS .. " --delete " .. local_cache .. "/ " .. server_path .. "/" + util.printout(cmd) + fs.execute(cmd) + return true + end + + if protocol ~= "rsync" then + return nil, "This command requires 'rsync', check your configuration." + end + + local srv, path = server_path:match("([^/]+)(/.+)") + local cmd = cfg.variables.RSYNC .. " " .. cfg.variables.RSYNCFLAGS .. " --delete -e ssh " .. local_cache .. "/ " .. user .. "@" .. srv .. ":" .. path .. "/" + + util.printout(cmd) + fs.execute(cmd) + + return true +end + +function admin_remove.command(args) + local server, server_table, err = cache.get_upload_server(args.server) + if not server then return nil, err end + return remove_files_from_server(not args.no_refresh, args.rocks, server, server_table) +end + + +return admin_remove diff --git a/src/luarocks/admin/index.lua b/src/luarocks/admin/index.lua new file mode 100644 index 00000000..e2e2deff --- /dev/null +++ b/src/luarocks/admin/index.lua @@ -0,0 +1,190 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local io = _tl_compat and _tl_compat.io or io; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local package = _tl_compat and _tl_compat.package or package; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table + +local index = {} + + +local util = require("luarocks.util") +local fs = require("luarocks.fs") +local vers = require("luarocks.core.vers") +local persist = require("luarocks.persist") +local dir = require("luarocks.dir") +local manif = require("luarocks.manif") + + + + + +local ext_url_target = ' target="_blank"' + +local index_header = [[ + + + +Available rocks + + + + +

Available rocks

+

+Lua modules available from this location for use with LuaRocks: +

+ +]] + +local index_package_begin = [[ + + + +]] + +local index_footer_begin = [[ +
+

$package - $summary
+

$detailed
+$externaldependencies +latest sources $homepage | License: $license

+
+]] + +local index_package_end = [[ +
+

+manifest file +]] +local index_manifest_ver = [[ +• Lua $VER manifest file (zip) +]] +local index_footer_end = [[ +

+ + +]] + +function index.format_external_dependencies(rockspec) + if rockspec.external_dependencies then + local deplist = {} + local listed_set = {} + local plats = nil + for name, desc in util.sortedpairs(rockspec.external_dependencies) do + if name ~= "platforms" then + table.insert(deplist, name:lower()) + listed_set[name] = true + else + plats = desc + end + end + if plats then + for plat, entries in util.sortedpairs(plats) do + for name, desc in util.sortedpairs(entries) do + if not listed_set[name] then + table.insert(deplist, name:lower() .. " (on " .. plat .. ")") + end + end + end + end + return '

External dependencies: ' .. table.concat(deplist, ', ') .. '

' + else + return "" + end +end + +function index.make_index(repo) + if not fs.is_dir(repo) then + return nil, "Cannot access repository at " .. repo + end + local manifest = manif.load_manifest(repo) + local out = io.open(dir.path(repo, "index.html"), "w") + + out:write(index_header) + for package, version_list in util.sortedpairs(manifest.repository) do + local latest_rockspec = nil + local output = index_package_begin + for version, data in util.sortedpairs(version_list, vers.compare_versions) do + local versions = {} + output = output .. version .. ': ' + table.sort(data, function(a, b) return a.arch < b.arch end) + for _, item in ipairs(data) do + local file + if item.arch == 'rockspec' then + file = ("%s-%s.rockspec"):format(package, version) + if not latest_rockspec then latest_rockspec = file end + else + file = ("%s-%s.%s.rock"):format(package, version, item.arch) + end + table.insert(versions, '' .. item.arch .. '') + end + output = output .. table.concat(versions, ', ') .. '
' + end + output = output .. index_package_end + if latest_rockspec then + local rockspec = persist.load_into_table(dir.path(repo, latest_rockspec)) + local descript = rockspec.description or {} + local vars = { + anchor = package, + package = rockspec.package, + original = rockspec.source.url, + summary = descript.summary or "", + detailed = descript.detailed or "", + license = descript.license or "N/A", + homepage = descript.homepage and ('| project homepage') or "", + externaldependencies = index.format_external_dependencies(rockspec), + } + vars.detailed = vars.detailed:gsub("\n\n", "

"):gsub("%s+", " ") + vars.detailed = vars.detailed:gsub("(https?://[a-zA-Z0-9%.%%-_%+%[%]=%?&/$@;:]+)", '%1') + output = output:gsub("$(%w+)", vars) + else + output = output:gsub("$anchor", package) + output = output:gsub("$package", package) + output = output:gsub("$(%w+)", "") + end + out:write(output) + end + out:write(index_footer_begin) + for ver in util.lua_versions() do + out:write((index_manifest_ver:gsub("$VER", ver))) + end + out:write(index_footer_end) + out:close() +end + +return index diff --git a/src/luarocks/build.lua b/src/luarocks/build.lua new file mode 100644 index 00000000..63082077 --- /dev/null +++ b/src/luarocks/build.lua @@ -0,0 +1,487 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local io = _tl_compat and _tl_compat.io or io; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local pairs = _tl_compat and _tl_compat.pairs or pairs; local pcall = _tl_compat and _tl_compat.pcall or pcall; local string = _tl_compat and _tl_compat.string or string +local build = {Builder = {}, } + + + + + + +local path = require("luarocks.path") +local util = require("luarocks.util") +local fun = require("luarocks.fun") +local fetch = require("luarocks.fetch") +local fs = require("luarocks.fs") +local dir = require("luarocks.dir") +local deps = require("luarocks.deps") +local cfg = require("luarocks.core.cfg") +local vers = require("luarocks.core.vers") +local repos = require("luarocks.repos") +local repo_writer = require("luarocks.repo_writer") +local deplocks = require("luarocks.deplocks") + + + + + + + + + + + + + +do + + + + 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 + + + + + + + + function build.apply_patches(rockspec) + + if not (rockspec.build.extra_files or rockspec.build.patches) then + return true + end + + local fd = io.open(fs.absolute_name(".luarocks.patches.applied"), "r") + if fd then + fd:close() + return true + end + + if rockspec.build.extra_files then + extract_from_rockspec(rockspec.build.extra_files) + end + if rockspec.build.patches then + extract_from_rockspec(rockspec.build.patches) + for patch, patchdata in util.sortedpairs(rockspec.build.patches) do + util.printout("Applying patch " .. patch .. "...") + local create_delete = rockspec:format_is_at_least("3.0") + local ok, err = fs.apply_patch(tostring(patch), patchdata, create_delete) + if not ok then + return nil, "Failed applying patch " .. patch + end + end + end + + fd = io.open(fs.absolute_name(".luarocks.patches.applied"), "w") + if fd then + fd:close() + end + return true + end +end + +local function check_macosx_deployment_target(rockspec) + local target = rockspec.build.macosx_deployment_target + local function patch_variable(var) + local rockspec_variables = rockspec.variables + 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.is_platform("macosx") and rockspec:format_is_at_least("3.0") and target then + local version = util.popen_read("sw_vers -productVersion") + if version:match("^%d+%.%d+%.%d+$") or version:match("^%d+%.%d+$") then + if vers.compare_versions(target, version) then + return nil, ("This rock requires Mac OSX %s, and you are running %s."):format(target, version) + end + end + patch_variable("CC") + patch_variable("LD") + end + return true +end + +local function process_dependencies(rockspec, opts, cwd) + if not opts.build_only_deps then + local ok, err, errcode = deps.check_external_deps(rockspec, "build") + if err then + return nil, err, errcode + end + end + + if opts.deps_mode == "none" then + return true + end + + local deplock_dir = fs.exists(dir.path(cwd, "luarocks.lock")) and cwd or nil + + if not opts.build_only_deps then + if next(rockspec.build_dependencies) then + + local user_lua_version = cfg.lua_version + local running_lua_version = _VERSION:sub(5) + + if running_lua_version ~= user_lua_version then + + + + + + + cfg.lua_version = running_lua_version + cfg.lua_modules_path = cfg.lua_modules_path:gsub(user_lua_version:gsub("%.", "%%."), running_lua_version) + cfg.lib_modules_path = cfg.lib_modules_path:gsub(user_lua_version:gsub("%.", "%%."), running_lua_version) + cfg.rocks_subdir = cfg.rocks_subdir:gsub(user_lua_version:gsub("%.", "%%."), running_lua_version) + path.use_tree(cfg.root_dir) + end + + local ok, err, errcode = deps.fulfill_dependencies(rockspec, "build_dependencies", "all", opts.verify, deplock_dir) + + path.add_to_package_paths(cfg.root_dir) + + if running_lua_version ~= user_lua_version then + + cfg.lua_version = user_lua_version + cfg.lua_modules_path = cfg.lua_modules_path:gsub(running_lua_version:gsub("%.", "%%."), user_lua_version) + cfg.lib_modules_path = cfg.lib_modules_path:gsub(running_lua_version:gsub("%.", "%%."), user_lua_version) + cfg.rocks_subdir = cfg.rocks_subdir:gsub(running_lua_version:gsub("%.", "%%."), user_lua_version) + path.use_tree(cfg.root_dir) + end + + if err then + return nil, err, errcode + end + end + end + + return deps.fulfill_dependencies(rockspec, "dependencies", opts.deps_mode, opts.verify, deplock_dir) +end + +local function fetch_and_change_to_source_dir(rockspec, opts) + if opts.minimal_mode or opts.build_only_deps then + return true + end + if opts.need_to_fetch then + if opts.branch then + rockspec.source.branch = opts.branch + end + local oks, source_dir, errcode = fetch.fetch_sources(rockspec, true) + if not oks then + return nil, source_dir, errcode + end + local ok, err + ok, err = fs.change_dir(source_dir) + if not ok then + return nil, err + end + else + if rockspec.source.file then + local ok, err = fs.unpack_archive(rockspec.source.file) + if not ok then + return nil, err + end + end + local ok, err = fetch.find_rockspec_source_dir(rockspec, ".") + if not ok then + return nil, err + end + end + fs.change_dir(rockspec.source.dir) + return true +end + +local function prepare_install_dirs(name, version) + local dirs = { + lua = { name = path.lua_dir(name, version), is_module_path = true, perms = "read" }, + lib = { name = path.lib_dir(name, version), is_module_path = true, perms = "exec" }, + bin = { name = path.bin_dir(name, version), is_module_path = false, perms = "exec" }, + conf = { name = path.conf_dir(name, version), is_module_path = false, perms = "read" }, + } + + for _, d in pairs(dirs) do + local ok, err = fs.make_dir(d.name) + if not ok then + return nil, err + end + end + + return dirs +end + +local function run_build_driver(rockspec, no_install) + local btype = rockspec.build.type + if btype == "none" then + return true + end + + if btype == "module" then + util.printout("Do not use 'module' as a build type. Use 'builtin' instead.") + btype = "builtin" + rockspec.build.type = btype + end + local driver + if cfg.accepted_build_types and not fun.contains(cfg.accepted_build_types, btype) then + return nil, "This rockspec uses the '" .. btype .. "' build type, which is blocked by the 'accepted_build_types' setting in your LuaRocks configuration." + end + local pok, driver_str = pcall(require, "luarocks.build." .. btype) + if not (type(driver_str) == "table") then + return nil, "Failed initializing build back-end for build type '" .. btype .. "': " .. driver_str + else + driver = driver_str + end + + if not driver.skip_lua_inc_lib_check then + local ok, err, errcode = deps.check_lua_incdir(rockspec.variables) + if not ok then + return nil, err, errcode + end + + if cfg.link_lua_explicitly then + ok, err, errcode = deps.check_lua_libdir(rockspec.variables) + if not ok then + return nil, err, errcode + end + end + end + + local ok, err = driver.run(rockspec, no_install) + if not ok then + return nil, "Build error: " .. err + end + return true +end + +local install_files +do + + + + + + + + + + + + + + + + + + local function install_to(files, location, is_module_path, perms) + if not files then + return true + end + 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(file, dir.path(dest, filename), perms) + if not ok then + return nil, "Failed copying " .. file + 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, "read") + break + end + end + end + end + + install_files = function(rockspec, dirs) + local name, version = rockspec.name, rockspec.version + + if rockspec.build.install then + for k, d in pairs(dirs) do + local ok, err = install_to((rockspec.build.install)[k], d.name, d.is_module_path, d.perms) + if not ok then return nil, err end + end + end + + local copy_directories = rockspec.build.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 ipairs(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 + + return true + end +end + + + + + + +function build.build_rockspec(rockspec, opts, cwd) + + cwd = cwd or dir.path(".") + + if not rockspec.build then + if rockspec:format_is_at_least("3.0") then + rockspec.build = { + type = "builtin", + } + else + return nil, "Rockspec error: build table not specified" + end + end + + if not rockspec.build.type then + if rockspec:format_is_at_least("3.0") then + rockspec.build.type = "builtin" + else + return nil, "Rockspec error: build type not specified" + end + end + + local ok, err = fetch_and_change_to_source_dir(rockspec, opts) + if not ok then return nil, err end + + if opts.pin then + deplocks.init(rockspec.name, ".") + end + + ok, err = process_dependencies(rockspec, opts, cwd) + if not ok then return nil, err end + + local name, version = rockspec.name, rockspec.version + if opts.build_only_deps then + if opts.pin then + deplocks.write_file() + end + return name, version + end + + local dirs, err + local rollback + if not opts.no_install then + if repos.is_installed(name, version) then + repo_writer.delete_version(name, version, opts.deps_mode) + end + + dirs, err = prepare_install_dirs(name, version) + if not dirs then return nil, err end + + rollback = util.schedule_function(function() + fs.delete(path.install_dir(name, version)) + fs.remove_dir_if_empty(path.versions_dir(name)) + end) + end + + ok, err = build.apply_patches(rockspec) + if not ok then return nil, err end + + ok, err = check_macosx_deployment_target(rockspec) + if not ok then return nil, err end + + ok, err = run_build_driver(rockspec, opts.no_install) + if not ok then return nil, err end + + if opts.no_install then + fs.pop_dir() + if opts.need_to_fetch then + fs.pop_dir() + end + return name, version + end + + ok, err = install_files(rockspec, dirs) + if not ok then return nil, err end + + for _, d in pairs(dirs) do + fs.remove_dir_if_empty(d.name) + end + + fs.pop_dir() + if opts.need_to_fetch then + fs.pop_dir() + end + + if opts.pin then + deplocks.write_file() + end + + fs.copy(rockspec.local_abs_filename, path.rockspec_file(name, version), "read") + + local deplock_file = deplocks.get_abs_filename(name) + if deplock_file then + fs.copy(deplock_file, dir.path(path.install_dir(name, version), "luarocks.lock"), "read") + end + + ok, err = repo_writer.deploy_files(name, version, repos.should_wrap_bin_scripts(rockspec), opts.deps_mode, opts.namespace) + if not ok then return nil, err end + + util.remove_scheduled_function(rollback) + rollback = util.schedule_function(function() + repo_writer.delete_version(name, version, opts.deps_mode) + end) + + ok, err = repos.run_hook(rockspec, "post_install") + if not ok then return nil, err end + + util.announce_install(rockspec) + util.remove_scheduled_function(rollback) + return name, version +end + +return build diff --git a/src/luarocks/build/builtin.lua b/src/luarocks/build/builtin.lua new file mode 100644 index 00000000..24434fef --- /dev/null +++ b/src/luarocks/build/builtin.lua @@ -0,0 +1,403 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local io = _tl_compat and _tl_compat.io or io; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local os = _tl_compat and _tl_compat.os or os; local package = _tl_compat and _tl_compat.package or package; local pairs = _tl_compat and _tl_compat.pairs or pairs; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table; local _tl_table_unpack = unpack or table.unpack + +local builtin = {} + + + + + + + + + + + + +builtin.skip_lua_inc_lib_check = true + +local dir_sep = package.config:sub(1, 1) + +local fs = require("luarocks.fs") +local path = require("luarocks.path") +local util = require("luarocks.util") +local cfg = require("luarocks.core.cfg") +local dir = require("luarocks.dir") +local deps = require("luarocks.deps") + +local function autoextract_libs(external_dependencies, variables) + if not external_dependencies then + return nil, nil, nil + end + local libs = {} + local incdirs = {} + local libdirs = {} + for name, data in pairs(external_dependencies) do + if data.library then + table.insert(libs, data.library) + table.insert(incdirs, variables[name .. "_INCDIR"]) + table.insert(libdirs, variables[name .. "_LIBDIR"]) + end + end + return libs, incdirs, libdirs +end + +do + local function get_cmod_name(file) + local fd = io.open(dir.path(fs.current_dir(), file), "r") + 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 skiplist = { + ["spec"] = true, + [".luarocks"] = true, + ["lua_modules"] = true, + ["test.lua"] = true, + ["tests.lua"] = true, + } + + function builtin.autodetect_modules(libs, incdirs, libdirs) + local modules = {} + local install + local copy_directories + + local prefix = "" + for _, parent in ipairs({ "src", "lua", "lib" }) do + if fs.is_dir(parent) then + fs.change_dir(parent) + prefix = parent .. dir_sep + break + end + end + + for _, file in ipairs(fs.find()) do + local base = file:match("^([^\\/]*)") + if not skiplist[base] then + local luamod = file:match("(.*)%.lua$") + if luamod then + 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"))) + modules[modname] = { + sources = prefix .. file, + libraries = libs, + incdirs = incdirs, + libdirs = libdirs, + } + end + end + end + end + + if prefix ~= "" then + fs.pop_dir() + end + + local bindir = (fs.is_dir(dir.path("src", "bin")) and dir.path("src", "bin")) or + (fs.is_dir("bin") and "bin") + if bindir then + install = { bin = {} } + for _, file in ipairs(fs.list_dir(bindir)) do + table.insert((install.bin), dir.path(bindir, file)) + end + end + + for _, directory in ipairs({ "doc", "docs", "samples", "tests" }) do + if fs.is_dir(directory) then + if not copy_directories then + copy_directories = {} + end + table.insert(copy_directories, directory) + end + end + + return modules, install, copy_directories + end +end + + + + +local function execute(...) + io.stdout:write(table.concat({ ... }, " ") .. "\n") + return fs.execute(...) +end + + + + + +function builtin.run(rockspec, no_install) + local compile_object + local compile_library + local compile_static_library + + local build = rockspec.build + local variables = rockspec.variables + local checked_lua_h = false + + for _, var in ipairs({ "CC", "CFLAGS", "LDFLAGS" }) do + variables[var] = variables[var] or os.getenv(var) or "" + end + + local function add_flags(extras, flag, flags) + if flags then + if not (type(flags) == "table") then + flags = { tostring(flags) } + end + util.variable_substitutions(flags, variables) + for _, v in ipairs(flags) do + table.insert(extras, flag:format(v)) + end + end + end + + if cfg.is_platform("mingw32") then + compile_object = function(object, source, defines, incdirs) + local extras = {} + add_flags(extras, "-D%s", defines) + add_flags(extras, "-I%s", incdirs) + return execute(variables.CC .. " " .. variables.CFLAGS, "-c", "-o", object, "-I" .. variables.LUA_INCDIR, source, _tl_table_unpack(extras)) + end + compile_library = function(library, objects, libraries, libdirs, name) + local extras = { _tl_table_unpack(objects) } + add_flags(extras, "-L%s", libdirs) + add_flags(extras, "-l%s", libraries) + extras[#extras + 1] = dir.path(variables.LUA_LIBDIR, variables.LUALIB) + + if variables.CC == "clang" or variables.CC == "clang-cl" then + local exported_name = name:gsub("%.", "_") + exported_name = exported_name:match('^[^%-]+%-(.+)$') or exported_name + extras[#extras + 1] = string.format("-Wl,-export:luaopen_%s", exported_name) + else + extras[#extras + 1] = "-l" .. (variables.MSVCRT or "m") + end + + local ok = execute(variables.LD .. " " .. variables.LDFLAGS .. " " .. variables.LIBFLAG, "-o", library, _tl_table_unpack(extras)) + return ok + end + + + + + + + + + + elseif cfg.is_platform("win32") then + compile_object = function(object, source, defines, incdirs) + local extras = {} + add_flags(extras, "-D%s", defines) + add_flags(extras, "-I%s", incdirs) + return execute(variables.CC .. " " .. variables.CFLAGS, "-c", "-Fo" .. object, "-I" .. variables.LUA_INCDIR, source, _tl_table_unpack(extras)) + end + compile_library = function(library, objects, libraries, libdirs, name) + local extras = { _tl_table_unpack(objects) } + add_flags(extras, "-libpath:%s", libdirs) + add_flags(extras, "%s.lib", libraries) + local basename = dir.base_name(library):gsub(".[^.]*$", "") + local deffile = basename .. ".def" + local def = io.open(dir.path(fs.current_dir(), deffile), "w+") + local exported_name = name:gsub("%.", "_") + exported_name = exported_name:match('^[^%-]+%-(.+)$') or exported_name + def:write("EXPORTS\n") + def:write("luaopen_" .. exported_name .. "\n") + def:close() + local ok = execute(variables.LD, "-dll", "-def:" .. deffile, "-out:" .. library, dir.path(variables.LUA_LIBDIR, variables.LUALIB), _tl_table_unpack(extras)) + local basedir = "" + if name:find("%.") then + basedir = name:gsub("%.%w+$", "\\") + basedir = basedir:gsub("%.", "\\") + end + local manifestfile = basedir .. basename .. ".dll.manifest" + + if ok and fs.exists(manifestfile) then + ok = execute(variables.MT, "-manifest", manifestfile, "-outputresource:" .. basedir .. basename .. ".dll;2") + end + return ok + end + + + + + + + else + compile_object = function(object, source, defines, incdirs) + local extras = {} + add_flags(extras, "-D%s", defines) + add_flags(extras, "-I%s", incdirs) + return execute(variables.CC .. " " .. variables.CFLAGS, "-I" .. variables.LUA_INCDIR, "-c", source, "-o", object, _tl_table_unpack(extras)) + end + compile_library = function(library, objects, libraries, libdirs) + local extras = { _tl_table_unpack(objects) } + add_flags(extras, "-L%s", libdirs) + if cfg.gcc_rpath then + add_flags(extras, "-Wl,-rpath,%s", libdirs) + end + add_flags(extras, "-l%s", libraries) + if cfg.link_lua_explicitly then + extras[#extras + 1] = "-L" .. variables.LUA_LIBDIR + extras[#extras + 1] = "-llua" + end + return execute(variables.LD .. " " .. variables.LDFLAGS .. " " .. variables.LIBFLAG, "-o", library, _tl_table_unpack(extras)) + end + compile_static_library = function(library, objects, libraries, libdirs, name) + local ok = execute(variables.AR, "rc", library, _tl_table_unpack(objects)) + if ok then + ok = execute(variables.RANLIB, library) + end + return ok + end + end + + local ok, err + local lua_modules = {} + local lib_modules = {} + local luadir = path.lua_dir(rockspec.name, rockspec.version) + local libdir = path.lib_dir(rockspec.name, rockspec.version) + + local autolibs, autoincdirs, autolibdirs = autoextract_libs(rockspec.external_dependencies, rockspec.variables) + + if not build.modules then + if rockspec:format_is_at_least("3.0") then + local install, copy_directories + build.modules, install, copy_directories = builtin.autodetect_modules(autolibs, autoincdirs, autolibdirs) + build.install = build.install or install + build.copy_directories = build.copy_directories or copy_directories + else + return nil, "Missing build.modules table" + end + end + + local compile_temp_dir + + local mkdir_cache = {} + local function cached_make_dir(name) + if name == "" or mkdir_cache[name] then + return true + end + mkdir_cache[name] = true + return fs.make_dir(name) + end + + for name, info in pairs(build.modules) do + local moddir = path.module_to_path(name) + if type(info) == "string" then + local ext = info:match("%.([^.]+)$") + if ext == "lua" then + local filename = dir.base_name(info) + if filename == "init.lua" and not name:match("%.init$") then + moddir = path.module_to_path(name .. ".init") + else + local basename = name:match("([^.]+)$") + filename = basename .. ".lua" + end + local dest = dir.path(luadir, moddir, filename) + lua_modules[info] = dest + else + info = { info } + end + end + if type(info) == "table" then + if not checked_lua_h then + local ok, err, errcode = deps.check_lua_incdir(rockspec.variables) + if not ok then + return nil, err, errcode + end + + if cfg.link_lua_explicitly then + local ok, err, errcode = deps.check_lua_libdir(rockspec.variables) + if not ok then + return nil, err, errcode + end + end + checked_lua_h = true + end + local objects = {} + local sources = info.sources + if info[1] then sources = info end + if type(sources) == "string" then sources = { sources } end + if not (type(sources) == "table") then + return nil, "error in rockspec: module '" .. name .. "' entry has no 'sources' list" + end + for _, source in ipairs(sources) do + if not (type(source) == "string") then + return nil, "error in rockspec: module '" .. name .. "' does not specify source correctly." + end + local object = source:gsub("%.[^.]*$", "." .. cfg.obj_extension) + if not object then + object = source .. "." .. cfg.obj_extension + end + ok = compile_object(object, source, info.defines, info.incdirs or autoincdirs) + if not ok then + return nil, "Failed compiling object " .. object + end + table.insert(objects, object) + end + + if not compile_temp_dir then + compile_temp_dir = fs.make_temp_dir("build-" .. rockspec.package .. "-" .. rockspec.version) + util.schedule_function(fs.delete, compile_temp_dir) + end + + local module_name = name:match("([^.]*)$") .. "." .. util.matchquote(cfg.lib_extension) + if moddir ~= "" then + module_name = dir.path(moddir, module_name) + end + + local build_name = dir.path(compile_temp_dir, module_name) + local build_dir = dir.dir_name(build_name) + cached_make_dir(build_dir) + + lib_modules[build_name] = dir.path(libdir, module_name) + ok = compile_library(build_name, objects, info.libraries, info.libdirs or autolibdirs, name) + if not ok then + return nil, "Failed compiling module " .. module_name + end + + + + if cached_make_dir(dir.dir_name(module_name)) then + fs.copy(build_name, module_name) + end + + + + + + + + + + + + + end + end + if not no_install then + for _, mods in ipairs({ { tbl = lua_modules, perms = "read" }, { tbl = lib_modules, perms = "exec" } }) do + for name, dest in pairs(mods.tbl) do + cached_make_dir(dir.dir_name(dest)) + ok, err = fs.copy(name, dest, mods.perms) + if not ok then + return nil, "Failed installing " .. name .. " in " .. dest .. ": " .. err + end + end + end + if fs.is_dir("lua") then + ok, err = fs.copy_contents("lua", luadir) + if not ok then + return nil, "Failed copying contents of 'lua' directory: " .. err + end + end + end + return true +end + +return builtin diff --git a/src/luarocks/build/cmake.lua b/src/luarocks/build/cmake.lua new file mode 100644 index 00000000..57d7535c --- /dev/null +++ b/src/luarocks/build/cmake.lua @@ -0,0 +1,91 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local assert = _tl_compat and _tl_compat.assert or assert; local io = _tl_compat and _tl_compat.io or io; local os = _tl_compat and _tl_compat.os or os; local pairs = _tl_compat and _tl_compat.pairs or pairs; local string = _tl_compat and _tl_compat.string or string + + + +local cmake = {CMakeBuild = {Install = {}, }, } + + + + + + + + + + +local fs = require("luarocks.fs") +local util = require("luarocks.util") +local cfg = require("luarocks.core.cfg") + + + + + + + +function cmake.run(rockspec, no_install) + local build = rockspec.build + local variables = build.variables or {} + + + variables.CMAKE_MODULE_PATH = os.getenv("CMAKE_MODULE_PATH") + variables.CMAKE_LIBRARY_PATH = os.getenv("CMAKE_LIBRARY_PATH") + variables.CMAKE_INCLUDE_PATH = os.getenv("CMAKE_INCLUDE_PATH") + + util.variable_substitutions(variables, rockspec.variables) + + local ok, err_msg = fs.is_tool_available(rockspec.variables.CMAKE, "CMake") + if not ok then + return nil, err_msg + end + + + local build_cmake = build.cmake + if type(build_cmake) == "string" then + local cmake_handler = assert((io.open(fs.current_dir() .. "/CMakeLists.txt", "w"))) + cmake_handler:write(build.cmake) + cmake_handler:close() + end + + + local args = "" + + + if cfg.cmake_generator then + args = args .. ' -G"' .. cfg.cmake_generator .. '"' + elseif cfg.is_platform("windows") and cfg.target_cpu:match("x86_64$") then + args = args .. " -DCMAKE_GENERATOR_PLATFORM=x64" + end + + for k, v in pairs(variables) do + args = args .. ' -D' .. k .. '="' .. tostring(v) .. '"' + end + + if not fs.execute_string(rockspec.variables.CMAKE .. " -H. -Bbuild.luarocks " .. args) then + return nil, "Failed cmake." + end + + local do_build, do_install + if rockspec:format_is_at_least("3.0") then + do_build = (build.build_pass == nil) and true or build.build_pass + do_install = (build.install_pass == nil) and true or build.install_pass + else + do_build = true + do_install = true + end + + if do_build then + if not fs.execute_string(rockspec.variables.CMAKE .. " --build build.luarocks --config Release") then + return nil, "Failed building." + end + end + if do_install and not no_install then + if not fs.execute_string(rockspec.variables.CMAKE .. " --build build.luarocks --target install --config Release") then + return nil, "Failed installing." + end + end + + return true +end + +return cmake diff --git a/src/luarocks/build/command.lua b/src/luarocks/build/command.lua new file mode 100644 index 00000000..f795321b --- /dev/null +++ b/src/luarocks/build/command.lua @@ -0,0 +1,51 @@ + + + + +local command = {CommandBuild = {Install = {}, }, } + + + + + + + + +local fs = require("luarocks.fs") +local util = require("luarocks.util") +local cfg = require("luarocks.core.cfg") + + + + + + + +function command.run(rockspec, not_install) + + local build = rockspec.build + + util.variable_substitutions(build, rockspec.variables) + + local env = { + CC = cfg.variables.CC, + + + } + + if build.build_command then + util.printout(build.build_command) + if not fs.execute_env(env, build.build_command) then + return nil, "Failed building." + end + end + if build.install_command and not not_install then + util.printout(build.install_command) + if not fs.execute_env(env, build.install_command) then + return nil, "Failed installing." + end + end + return true +end + +return command diff --git a/src/luarocks/build/make.lua b/src/luarocks/build/make.lua new file mode 100644 index 00000000..3110198c --- /dev/null +++ b/src/luarocks/build/make.lua @@ -0,0 +1,107 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local pairs = _tl_compat and _tl_compat.pairs or pairs; local table = _tl_compat and _tl_compat.table or table; local _tl_table_unpack = unpack or table.unpack + + + +local make = {MakeBuild = {Install = {}, }, } + + + + + + + + + + + + + + +local fs = require("luarocks.fs") +local util = require("luarocks.util") +local cfg = require("luarocks.core.cfg") + + + + + + + + + + + + + +local function make_pass(make_cmd, pass, target, variables) + local assignments = {} + for k, v in pairs(variables) do + table.insert(assignments, k .. "=" .. v) + end + if pass then + return fs.execute(make_cmd .. " " .. target, _tl_table_unpack(assignments)) + else + return true + end +end + + + + + +function make.run(rockspec, not_install) + + local build = rockspec.build + + if build.build_pass == nil then build.build_pass = true end + if build.install_pass == nil then build.install_pass = true end + build.build_variables = build.build_variables or {} + build.install_variables = build.install_variables or {} + build.build_target = build.build_target or "" + build.install_target = build.install_target or "install" + local makefile = build.makefile or cfg.makefile + if makefile then + + build.build_target = "-f " .. makefile .. " " .. build.build_target + build.install_target = "-f " .. makefile .. " " .. build.install_target + end + + if build.variables then + for var, val in pairs(build.variables) do + build.build_variables[var] = val + build.install_variables[var] = val + end + end + + util.warn_if_not_used(build.build_variables, { CFLAGS = true }, "variable %s was not passed in build_variables") + util.variable_substitutions(build.build_variables, rockspec.variables) + util.variable_substitutions(build.install_variables, rockspec.variables) + + local auto_variables = { "CC" } + + for _, variable in ipairs(auto_variables) do + if not build.build_variables[variable] then + build.build_variables[variable] = rockspec.variables[variable] + end + if not build.install_variables[variable] then + build.install_variables[variable] = rockspec.variables[variable] + end + end + + + local make_cmd = cfg.make or rockspec.variables.MAKE + + local ok = make_pass(make_cmd, build.build_pass, build.build_target, build.build_variables) + if not ok then + return nil, "Failed building." + end + if not not_install then + ok = make_pass(make_cmd, build.install_pass, build.install_target, build.install_variables) + if not ok then + return nil, "Failed installing." + end + end + return true +end + +return make diff --git a/src/luarocks/cmd.lua b/src/luarocks/cmd.lua new file mode 100644 index 00000000..e9c81a0f --- /dev/null +++ b/src/luarocks/cmd.lua @@ -0,0 +1,807 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local assert = _tl_compat and _tl_compat.assert or assert; local debug = _tl_compat and _tl_compat.debug or debug; local io = _tl_compat and _tl_compat.io or io; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local loadfile = _tl_compat and _tl_compat.loadfile or loadfile; local os = _tl_compat and _tl_compat.os or os; local package = _tl_compat and _tl_compat.package or package; local pairs = _tl_compat and _tl_compat.pairs or pairs; local pcall = _tl_compat and _tl_compat.pcall or pcall; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table; local _tl_table_pack = table.pack or function(...) return { n = select("#", ...), ... } end; local _tl_table_unpack = unpack or table.unpack; local xpcall = _tl_compat and _tl_compat.xpcall or xpcall + +local cmd = {Module = {}, } + + + + + + + + + + + +local manif = require("luarocks.manif") +local config = require("luarocks.config") +local util = require("luarocks.util") +local path = require("luarocks.path") +local cfg = require("luarocks.core.cfg") +local dir = require("luarocks.dir") +local fun = require("luarocks.fun") +local fs = require("luarocks.fs") +local argparse = require("luarocks.vendor.argparse") + + + + + + + + + + + + + +local hc_ok, hardcoded = pcall(require, "luarocks.core.hardcoded") +if not hc_ok then + hardcoded = {} +end + +local program = util.this_program("luarocks") + +cmd.errorcodes = { + OK = 0, + UNSPECIFIED = 1, + PERMISSIONDENIED = 2, + CONFIGFILE = 3, + LOCK = 4, + CRASH = 99, +} + +local function check_popen() + local popen_ok, popen_result = pcall(io.popen, "") + if popen_ok then + if popen_result then + popen_result:close() + end + else + io.stderr:write("Your version of Lua does not support io.popen,\n") + io.stderr:write("which is required by LuaRocks. Please check your Lua installation.\n") + os.exit(cmd.errorcodes.UNSPECIFIED) + end +end + +local process_tree_args +do + local function replace_tree(args, root, tree) + root = dir.normalize(root) + args.tree = root + path.use_tree(tree or root) + end + + local function strip_trailing_slashes() + local cfg_root_dir = cfg.root_dir + if type(cfg_root_dir) == "string" then + cfg.root_dir = (cfg.root_dir):gsub("/+$", "") + else + (cfg.root_dir).root = (cfg.root_dir).root:gsub("/+$", "") + end + cfg.rocks_dir = cfg.rocks_dir:gsub("/+$", "") + cfg.deploy_bin_dir = cfg.deploy_bin_dir:gsub("/+$", "") + cfg.deploy_lua_dir = cfg.deploy_lua_dir:gsub("/+$", "") + cfg.deploy_lib_dir = cfg.deploy_lib_dir:gsub("/+$", "") + end + + local function set_named_tree(args, name) + for _, tree in ipairs(cfg.rocks_trees) do + if type(tree) == "table" and name == tree.name then + if not tree.root then + return nil, "Configuration error: tree '" .. tree.name .. "' has no 'root' field." + end + replace_tree(args, tree.root, tree) + return true + end + end + return false + end + + process_tree_args = function(args, project_dir) + + if args.global then + local ok, err = set_named_tree(args, "system") + if not ok then + return nil, err + end + elseif args.tree then + local named = set_named_tree(args, args.tree) + if not named then + local root_dir = fs.absolute_name(args.tree) + replace_tree(args, root_dir) + if (args.deps_mode or cfg.deps_mode) ~= "order" then + table.insert(cfg.rocks_trees, 1, { name = "arg", root = root_dir }) + end + end + elseif args["local"] then + if fs.is_superuser() then + return nil, "The --local flag is meant for operating in a user's home directory.\n" .. + "You are running as a superuser, which is intended for system-wide operation.\n" .. + "To force using the superuser's home, use --tree explicitly." + else + local ok, err = set_named_tree(args, "user") + if not ok then + return nil, err + end + end + elseif args.project_tree then + local tree = args.project_tree + table.insert(cfg.rocks_trees, 1, { name = "project", root = tree }) + manif.load_rocks_tree_manifests() + path.use_tree(tree) + elseif project_dir then + local project_tree = project_dir .. "/lua_modules" + table.insert(cfg.rocks_trees, 1, { name = "project", root = project_tree }) + manif.load_rocks_tree_manifests() + path.use_tree(project_tree) + elseif cfg.local_by_default then + local ok, err = set_named_tree(args, "user") + if not ok then + return nil, err + end + else + local trees = cfg.rocks_trees + path.use_tree(trees[#trees]) + end + + strip_trailing_slashes() + + cfg.variables.ROCKS_TREE = cfg.rocks_dir + cfg.variables.SCRIPTS_DIR = cfg.deploy_bin_dir + + return true + end +end + +local function process_server_args(args) + if args.server then + local protocol, pathname = dir.split_url(args.server) + table.insert(cfg.rocks_servers, 1, protocol .. "://" .. pathname) + end + + if args.dev then + for i, server in ipairs(cfg.rocks_servers) do + if type(server) == "string" then + cfg.rocks_servers[i] = dir.path(server, "dev") + else + for j, mirror in ipairs(server) do + server[j] = dir.path(mirror, "dev") + end + end + end + end + + if args.only_server then + if args.dev then + return nil, "--only-server cannot be used with --dev" + end + if args.server then + return nil, "--only-server cannot be used with --server" + end + cfg.rocks_servers = { args.only_server } + end + + return true +end + +local function error_handler(err) + if not debug then + return err + end + local mode = "Arch.: " .. (cfg and cfg.arch or "unknown") + if package.config:sub(1, 1) == "\\" then + if cfg and cfg.fs_use_modules then + mode = mode .. " (fs_use_modules = true)" + end + end + if cfg and cfg.is_binary then + mode = mode .. " (binary)" + end + return debug.traceback("LuaRocks " .. cfg.program_version .. + " bug (please report at https://github.com/luarocks/luarocks/issues).\n" .. + mode .. "\n" .. err, 2) +end + + + + +local function die(message, exitcode) + assert(type(message) == "string", "bad error, expected string, got: " .. type(message)) + assert(exitcode == nil or type(exitcode) == "number", "bad error, expected number, got: " .. type(exitcode) .. " - " .. tostring(exitcode)) + util.printerr("\nError: " .. message) + + local ok, err = xpcall(util.run_scheduled_functions, error_handler) + if not ok then + util.printerr("\nError: " .. err) + exitcode = cmd.errorcodes.CRASH + end + + os.exit(exitcode or cmd.errorcodes.UNSPECIFIED) +end + +local function search_lua(lua_version, verbose, search_at) + if search_at then + return util.find_lua(search_at, lua_version, verbose) + end + + local path_sep = (package.config:sub(1, 1) == "\\" and ";" or ":") + local all_tried = {} + for bindir in (os.getenv("PATH") or ""):gmatch("[^" .. path_sep .. "]+") do + local searchdir = (bindir:gsub("[\\/]+bin[\\/]?$", "")) + local detected, tried = util.find_lua(searchdir, lua_version) + if detected then + return detected + else + table.insert(all_tried, tried) + end + end + return nil, "Could not find " .. + (lua_version and "Lua " .. lua_version or "Lua") .. + " in PATH." .. + (verbose and " Tried:\n" .. table.concat(all_tried, "\n") or "") +end + +local init_config +do + local detect_config_via_args + do + local function find_project_dir(project_tree) + if project_tree then + return project_tree:gsub("[/\\][^/\\]+$", ""), true + else + local try = "." + for _ = 1, 10 do + if util.exists(try .. "/.luarocks") and util.exists(try .. "/lua_modules") then + return dir.normalize(try), false + elseif util.exists(try .. "/.luarocks-no-project") then + break + end + try = try .. "/.." + end + end + return nil + end + + local function find_default_lua_version(args, project_dir) + if hardcoded.FORCE_CONFIG then + return nil + end + + local dirs = {} + if project_dir then + table.insert(dirs, dir.path(project_dir, ".luarocks")) + end + if cfg.homeconfdir then + table.insert(dirs, cfg.homeconfdir) + end + table.insert(dirs, cfg.sysconfdir) + for _, d in ipairs(dirs) do + local f = dir.path(d, "default-lua-version.lua") + local mod, _ = loadfile(f, "t") + if mod then + local pok, ver = pcall(mod) + if pok and type(ver) == "string" and ver:match("%d+.%d+") then + if args.verbose then + util.printout("Defaulting to Lua " .. ver .. " based on " .. f .. " ...") + end + return ver + end + end + end + return nil + end + + local function find_version_from_config(dirname) + return fun.find(util.lua_versions("descending"), function(v) + if util.exists(dir.path(dirname, ".luarocks", "config-" .. v .. ".lua")) then + return v + end + end) + end + + local function detect_lua_via_args(args, project_dir) + local lua_version = args.lua_version or + find_default_lua_version(args, project_dir) or + (project_dir and find_version_from_config(project_dir)) + + if args.lua_dir then + local detected, err = util.find_lua(args.lua_dir, lua_version) + if not detected then + local suggestion = (not args.lua_version) and + "\nYou may want to specify a different Lua version with --lua-version\n" or + "" + die(err .. suggestion) + end + return detected + end + + if lua_version then + local detected = search_lua(lua_version) + if detected then + return detected + end + return { + lua_version = lua_version, + } + end + + return {} + end + + detect_config_via_args = function(args) + local project_dir, given + if not args.no_project then + project_dir, given = find_project_dir(args.project_tree) + end + + local detected = detect_lua_via_args(args, project_dir) + if args.lua_version then + detected.given_lua_version = args.lua_version + end + if args.lua_dir then + detected.given_lua_dir = args.lua_dir + end + if given then + detected.given_project_dir = project_dir + end + detected.project_dir = project_dir + return detected + end + end + + init_config = function(args) + local detected = detect_config_via_args(args) + + local ok, err = cfg.init(detected, util.warning) + if not ok then + return nil, err + end + + return (detected.lua_dir ~= nil) + end +end + +local variables_help = [[ +Variables: + Variables from the "variables" table of the configuration file can be + overridden with VAR=VALUE assignments. + +]] + +local lua_example = package.config:sub(1, 1) == "\\" and +"" or +"" + +local function show_status(file, status, err) + return (file and file .. " " or "") .. (status and "(ok)" or ("(" .. (err or "not found") .. ")")) +end + +local function use_to_fix_location(key, what) + local buf = " ****************************************\n" + buf = buf .. " Use the command\n\n" + buf = buf .. " luarocks config " .. key .. " " .. (what or "

") .. "\n\n" + buf = buf .. " to fix the location\n" + buf = buf .. " ****************************************\n" + return buf +end + +local function get_config_text(cfg) + local deps = require("luarocks.deps") + + local libdir_ok = deps.check_lua_libdir(cfg.variables) + local incdir_ok = deps.check_lua_incdir(cfg.variables) + local lua_ok = cfg.variables.LUA and fs.exists(cfg.variables.LUA) + + local buf = "Configuration:\n" + buf = buf .. " Lua:\n" + buf = buf .. " Version : " .. cfg.lua_version .. "\n" + if cfg.luajit_version then + buf = buf .. " LuaJIT : " .. cfg.luajit_version .. "\n" + end + buf = buf .. " LUA : " .. show_status(cfg.variables.LUA, lua_ok, "interpreter not found") .. "\n" + if not lua_ok then + buf = buf .. use_to_fix_location("variables.LUA", lua_example) + end + buf = buf .. " LUA_INCDIR : " .. show_status(cfg.variables.LUA_INCDIR, incdir_ok, "lua.h not found") .. "\n" + if lua_ok and not incdir_ok then + buf = buf .. use_to_fix_location("variables.LUA_INCDIR") + end + buf = buf .. " LUA_LIBDIR : " .. show_status(cfg.variables.LUA_LIBDIR, libdir_ok, "Lua library itself not found") .. "\n" + if lua_ok and not libdir_ok then + buf = buf .. use_to_fix_location("variables.LUA_LIBDIR") + end + + buf = buf .. "\n Configuration files:\n" + local conf = cfg.config_files + buf = buf .. " System : " .. show_status(fs.absolute_name(conf.system.file), conf.system.found) .. "\n" + if conf.user.file then + buf = buf .. " User : " .. show_status(fs.absolute_name(conf.user.file), conf.user.found) .. "\n" + else + buf = buf .. " User : disabled in this LuaRocks installation.\n" + end + if conf.project then + buf = buf .. " Project : " .. show_status(fs.absolute_name(conf.project.file), conf.project.found) .. "\n" + end + buf = buf .. "\n Rocks trees in use: \n" + for _, tree in ipairs(cfg.rocks_trees) do + if type(tree) == "string" then + buf = buf .. " " .. fs.absolute_name(tree) + else + local name = tree.name and " (\"" .. tree.name .. "\")" or "" + buf = buf .. " " .. fs.absolute_name(tree.root) .. name + end + buf = buf .. "\n" + end + + return buf +end + +local function get_parser(description, cmd_modules) + local basename = dir.base_name(program) + local parser = argparse( + basename, "LuaRocks " .. cfg.program_version .. ", the Lua package manager\n\n" .. + program .. " - " .. description, variables_help .. "Run '" .. basename .. + "' without any arguments to see the configuration."): + help_max_width(80): + add_help_command(): + add_complete_command({ + help_max_width = 100, + summary = "Output a shell completion script.", + description = [[ +Output a shell completion script. + +Enabling completions for Bash: + + Add the following line to your ~/.bashrc: + source <(]] .. basename .. [[ completion bash) + or save the completion script to the local completion directory: + ]] .. basename .. [[ completion bash > ~/.local/share/bash-completion/completions/]] .. basename .. [[ + + +Enabling completions for Zsh: + + Save the completion script to a file in your $fpath. + You can add a new directory to your $fpath by adding e.g. + fpath=(~/.zfunc $fpath) + to your ~/.zshrc. + Then run: + ]] .. basename .. [[ completion zsh > ~/.zfunc/_]] .. basename .. [[ + + +Enabling completion for Fish: + + Add the following line to your ~/.config/fish/config.fish: + ]] .. basename .. [[ completion fish | source + or save the completion script to the local completion directory: + ]] .. basename .. [[ completion fish > ~/.config/fish/completions/]] .. basename .. [[.fish +]], }): + command_target("command"): + require_command(false) + + parser:flag("--version", "Show version info and exit."): + action(function() + util.printout(program .. " " .. cfg.program_version) + util.printout(description) + util.printout() + os.exit(cmd.errorcodes.OK) + end) + parser:flag("--dev", "Enable the sub-repositories in rocks servers for " .. + "rockspecs of in-development versions.") + parser:option("--server", "Fetch rocks/rockspecs from this server " .. + "(takes priority over config file)."): + hidden_name("--from") + parser:option("--only-server", "Fetch rocks/rockspecs from this server only " .. + "(overrides any entries in the config file)."): + argname(""): + hidden_name("--only-from") + parser:option("--only-sources", "Restrict downloads to paths matching the given URL."): + argname(""): + hidden_name("--only-sources-from") + parser:option("--namespace", "Specify the rocks server namespace to use."): + convert(string.lower) + parser:option("--lua-dir", "Which Lua installation to use."): + argname("") + parser:option("--lua-version", "Which Lua version to use."): + argname(""): + convert(function(s) return (s:match("^%d+%.%d+$")) end) + parser:option("--tree", "Which tree to operate on."): + hidden_name("--to") + parser:flag("--local", "Use the tree in the user's home directory.\n" .. + "To enable it, see '" .. program .. " help path'.") + parser:flag("--global", "Use the system tree when `local_by_default` is `true`.") + parser:flag("--no-project", "Do not use project tree even if running from a project folder.") + parser:flag("--force-lock", "Attempt to overwrite the lock for commands " .. + "that require exclusive access, such as 'install'") + parser:flag("--verbose", "Display verbose output of commands executed.") + parser:option("--timeout", "Timeout on network operations, in seconds.\n" .. + "0 means no timeout (wait forever). Default is " .. + tostring(cfg.connection_timeout) .. "."): + argname(""): + convert(tonumber) + + + parser:option("--project-tree"):hidden(true) + + for _, module in util.sortedpairs(cmd_modules) do + module.add_to_parser(parser) + end + + return parser +end + +local function get_first_arg() + if not arg then + return + end + local first_arg = arg[0] + local i = -1 + while arg[i] do + first_arg = arg[i] + i = i - 1 + end + return first_arg +end + + + + + + + + + +function cmd.run_command(description, commands, external_namespace, ...) + + check_popen() + + + cfg.init() + + fs.init() + + for _, module_name in ipairs(fs.modules(external_namespace)) do + if not commands[module_name] then + commands[module_name] = external_namespace .. "." .. module_name + end + end + + local cmd_modules = {} + for name, module in pairs(commands) do + local pok, mod = pcall(require, module) + if pok and type(mod) == "table" then + local original_command = mod.command + if original_command then + if not mod.add_to_parser then + mod.add_to_parser = function(parser) + parser:command(name, mod.help, util.see_also()): + summary(mod.help_summary): + handle_options(false): + argument("input"): + args("*") + end + + mod.command = function(args) + return original_command(args, _tl_table_unpack(args.input)) + end + end + cmd_modules[name] = mod + else + util.warning("command module " .. module .. " does not implement command(), skipping") + end + else + util.warning("failed to load command module " .. module .. ": " .. tostring(mod)) + end + end + + local function process_cmdline_vars(...) + local args = _tl_table_pack(...) + local cmdline_vars = {} + local last = args.n + for i = 1, args.n do + if args[i] == "--" then + last = i - 1 + break + end + end + for i = last, 1, -1 do + local arg = args[i] + if arg:match("^[^-][^=]*=") then + local var, val = arg:match("^([A-Z_][A-Z0-9_]*)=(.*)") + if val then + cmdline_vars[var] = val + table.remove(args, i) + else + die("Invalid assignment: " .. arg) + end + end + end + + return args, cmdline_vars + end + + local cmdline_args, cmdline_vars = process_cmdline_vars(...) + local parser = get_parser(description, cmd_modules) + local args = parser:parse(cmdline_args) + + + if args.nodeps then + args.deps_mode = "none" + end + + if args.timeout then + cfg.connection_timeout = args.timeout + end + + if args.command == "config" then + if args.key == "lua_version" and args.value then + args.lua_version = args.value + elseif args.key == "lua_dir" and args.value then + args.lua_dir = args.value + end + end + + + local lua_found, err = init_config(args) + if err then + die(err) + end + + + + + fs.init() + + + + local tried + if not lua_found then + local detected + detected, tried = search_lua(cfg.lua_version, args.verbose, cfg.variables.LUA_DIR) + if detected then + lua_found = true + cfg.variables.LUA = detected.lua + cfg.variables.LUA_DIR = detected.lua_dir + cfg.variables.LUA_BINDIR = detected.lua_bindir + if args.lua_dir then + cfg.variables.LUA_INCDIR = nil + cfg.variables.LUA_LIBDIR = nil + end + else + cfg.variables.LUA = nil + cfg.variables.LUA_DIR = nil + cfg.variables.LUA_BINDIR = nil + cfg.variables.LUA_INCDIR = nil + cfg.variables.LUA_LIBDIR = nil + end + end + + if lua_found then + assert(cfg.variables.LUA) + else + + + + + if not cfg.variables.LUA then + local first_arg = get_first_arg() + local bin_dir = dir.dir_name(fs.absolute_name(first_arg)) + local exe = dir.base_name(first_arg) + exe = exe:match("rocks") and ("lua" .. (cfg.arch:match("win") and ".exe" or "")) or exe + local full_path = dir.path(bin_dir, exe) + if util.check_lua_version(full_path, cfg.lua_version) then + cfg.variables.LUA = dir.path(bin_dir, exe) + cfg.variables.LUA_DIR = bin_dir:gsub("[/\\]bin[/\\]?$", "") + cfg.variables.LUA_BINDIR = bin_dir + cfg.variables.LUA_INCDIR = nil + cfg.variables.LUA_LIBDIR = nil + end + end + end + + cfg.lua_found = lua_found + + if cfg.project_dir then + cfg.project_dir = fs.absolute_name(cfg.project_dir) + end + + if args.verbose then + cfg.verbose = true + print(("-"):rep(79)) + print("Current configuration:") + print(("-"):rep(79)) + print(config.to_string(cfg)) + print(("-"):rep(79)) + fs.verbose() + end + + if (not fs.current_dir()) or fs.current_dir() == "" then + die("Current directory does not exist. Please run LuaRocks from an existing directory.") + end + + local ok, err = process_tree_args(args, cfg.project_dir) + if not ok then + die(err) + end + + ok, err = process_server_args(args) + if not ok then + die(err) + end + + if args.only_sources then + cfg.only_sources_from = args.only_sources + end + + for k, v in pairs(cmdline_vars) do + cfg.variables[k] = v + end + + + if fs.is_superuser() then + cfg.local_cache = dir.path(fs.system_cache_dir(), "luarocks") + end + + if args.no_manifest then + cfg.no_manifest = true + end + + if not args.command then + parser:epilog(variables_help .. get_config_text(cfg)) + util.printout() + util.printout(parser:get_help()) + util.printout() + os.exit(cmd.errorcodes.OK) + end + + if not cfg.variables["LUA"] and args.command ~= "config" and args.command ~= "help" then + local flag = (not cfg.project_tree) and + "--local " or + "" + if args.lua_version then + flag = "--lua-version=" .. args.lua_version .. " " .. flag + end + die((tried or "Lua interpreter not found.") .. + "\nPlease set your Lua interpreter with:\n\n" .. + " luarocks " .. flag .. "config variables.LUA " .. lua_example .. "\n") + end + + local cmd_mod = cmd_modules[args.command] + + local lock + if cmd_mod.needs_lock and cmd_mod.needs_lock(args) then + local ok, err = fs.check_command_permissions(args) + if not ok then + die(err, cmd.errorcodes.PERMISSIONDENIED) + end + + lock, err = fs.lock_access(path.root_dir(cfg.root_dir), args.force_lock) + if not lock then + err = args.force_lock and + ("failed to force the lock" .. (err and ": " .. err or "")) or + (err and err ~= "File exists") and + err or + "try --force-lock to overwrite the lock" + + die("command '" .. args.command .. "' " .. + "requires exclusive write access to " .. path.root_dir(cfg.root_dir) .. " - " .. + err, cmd.errorcodes.LOCK) + end + end + + local call_ok, ok, err, exitcode = xpcall(function() + return cmd_mod.command(args) + end, error_handler) + + if lock then + fs.unlock_access(lock) + end + + if not call_ok then + die(tostring(ok), cmd.errorcodes.CRASH) + elseif not ok then + die(err, exitcode) + end + util.run_scheduled_functions() +end + +return cmd diff --git a/src/luarocks/cmd/build.lua b/src/luarocks/cmd/build.lua new file mode 100644 index 00000000..fb894c20 --- /dev/null +++ b/src/luarocks/cmd/build.lua @@ -0,0 +1,211 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local string = _tl_compat and _tl_compat.string or string + + +local cmd_build = {} + + + +local pack = require("luarocks.pack") +local path = require("luarocks.path") +local dir = require("luarocks.dir") +local util = require("luarocks.util") +local fetch = require("luarocks.fetch") +local fs = require("luarocks.fs") +local deps = require("luarocks.deps") +local remove = require("luarocks.remove") +local cfg = require("luarocks.core.cfg") +local build = require("luarocks.build") +local search = require("luarocks.search") +local make = require("luarocks.cmd.make") +local repos = require("luarocks.repos") + + + + + + + + + +function cmd_build.add_to_parser(parser) + local cmd = parser:command("build", "Build and install a rock, compiling its C parts if any.\n" .. + "If the sources contain a luarocks.lock file, uses it as an authoritative source for " .. + "exact version of dependencies.\n" .. + "If no arguments are given, behaves as luarocks make.", util.see_also()): + summary("Build/compile a rock.") + + cmd:argument("rock", "A rockspec file, a source rock file, or the name of " .. + "a rock to be fetched from a repository."): + args("?"): + action(util.namespaced_name_action) + cmd:argument("version", "Rock version."): + args("?") + + cmd:flag("--only-deps --deps-only", "Install only the dependencies of the rock.") + cmd:option("--branch", "Override the `source.branch` field in the loaded " .. + "rockspec. Allows to specify a different branch to fetch. Particularly " .. + 'for "dev" rocks.'): + argname("") + cmd:flag("--pin", "Create a luarocks.lock file listing the exact " .. + "versions of each dependency found for this rock (recursively), " .. + "and store it in the rock's directory. " .. + "Ignores any existing luarocks.lock file in the rock's sources.") + make.cmd_options(cmd) +end + + + + + + +local function build_rock(rock_filename, opts) + + local cwd = fs.absolute_name(dir.path(".")) + + local ok, err, errcode + + local unpack_dir + unpack_dir, err, errcode = fetch.fetch_and_unpack_rock(rock_filename, nil, opts.verify) + if not unpack_dir then + return nil, err, errcode + end + + local rockspec_filename = path.rockspec_name_from_rock(rock_filename) + + ok, err = fs.change_dir(unpack_dir) + if not ok then return nil, err end + + local rockspec + rockspec, err, errcode = fetch.load_rockspec(rockspec_filename) + if not rockspec then + return nil, err, errcode + end + + local n, v = build.build_rockspec(rockspec, opts, cwd) + + ok, err, errcode = n ~= nil, v, nil + + fs.pop_dir() + return ok, err, errcode +end + +local function do_build(name, namespace, version, opts) + + local url, err + if name:match("%.rockspec$") or name:match("%.rock$") then + url = name + else + url, err = search.find_src_or_rockspec(name, namespace, version, opts.check_lua_versions) + if not url then + return nil, err + end + end + + name, version = path.parse_name(url) + if name and repos.is_installed(name, version) then + if not opts.rebuild then + util.printout(name .. " " .. version .. " is already installed in " .. path.root_dir(cfg.root_dir)) + util.printout("Use --force to reinstall.") + return name, version, "skip" + end + end + + if url:match("%.rockspec$") then + local cwd = fs.absolute_name(dir.path(".")) + local rockspec, err = fetch.load_rockspec(url, nil, opts.verify) + if not rockspec then + return nil, err + end + return build.build_rockspec(rockspec, opts, cwd) + end + + if url:match("%.src%.rock$") then + opts.need_to_fetch = false + end + + local ok, err, errcode = build_rock(url, opts) + if not ok then + return nil, err, errcode + end + return name, version +end + + + + + + + +function cmd_build.command(args) + if not args.rock then + return make.command(args) + end + + local opts = { + need_to_fetch = true, + minimal_mode = false, + deps_mode = deps.get_deps_mode(args), + build_only_deps = not not (args.only_deps and not args.pack_binary_rock), + namespace = args.namespace, + branch = args.branch, + verify = not not args.verify, + check_lua_versions = not not args.check_lua_versions, + pin = not not args.pin, + rebuild = not not (args.force or args.force_fast), + no_install = false, + } + + if args.sign and not args.pack_binary_rock then + return nil, "In the build command, --sign is meant to be used only with --pack-binary-rock" + end + + if args.pack_binary_rock then + return pack.pack_binary_rock(args.rock, args.namespace, args.version, args.sign, function() + local name, version = do_build(args.rock, args.namespace, args.version, opts) + if name and args.no_doc then + util.remove_doc_dir(name, version) + end + return name, version + end) + end + + local name, version, skip = do_build(args.rock, args.namespace, args.version, opts) + if not name then + return nil, version + end + if skip == "skip" then + return name ~= nil, version + end + + if args.no_doc then + util.remove_doc_dir(name, version) + end + + if opts.build_only_deps then + util.printout("Stopping after installing dependencies for " .. name .. " " .. version) + util.printout() + else + if (not args.keep) and not cfg.keep_other_versions then + local ok, err, warn = remove.remove_other_versions(name, version, args.force, args.force_fast) + if not ok then + return nil, err + elseif warn then + util.printerr(err) + end + end + end + + if opts.deps_mode ~= "none" then + deps.check_dependencies(nil, deps.get_deps_mode(args)) + end + return name ~= nil, version +end + +cmd_build.needs_lock = function(args) + if args.pack_binary_rock then + return false + end + return true +end + +return cmd_build diff --git a/src/luarocks/cmd/config.lua b/src/luarocks/cmd/config.lua new file mode 100644 index 00000000..b0b04913 --- /dev/null +++ b/src/luarocks/cmd/config.lua @@ -0,0 +1,402 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local io = _tl_compat and _tl_compat.io or io; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local pairs = _tl_compat and _tl_compat.pairs or pairs; local string = _tl_compat and _tl_compat.string or string + +local config_cmd = {} + + +local persist = require("luarocks.persist") +local config = require("luarocks.config") +local cfg = require("luarocks.core.cfg") +local util = require("luarocks.util") +local deps = require("luarocks.deps") +local dir = require("luarocks.dir") +local fs = require("luarocks.fs") +local json = require("luarocks.vendor.dkjson") + + + + + + + +function config_cmd.add_to_parser(parser) + local cmd = parser:command("config", [[ +Query information about the LuaRocks configuration. + +* When given a configuration key, it prints the value of that key according to + the currently active configuration (taking into account all config files and + any command-line flags passed) + + Examples: + luarocks config variables.LUA_INCDIR + luarocks config lua_version + +* When given a configuration key and a value, it overwrites the config file (see + the --scope option below to determine which) and replaces the value of the + given key with the given value. + + * `lua_dir` is a special key as it checks for a valid Lua installation + (equivalent to --lua-dir) and sets several keys at once. + * `lua_version` is a special key as it changes the default Lua version + used by LuaRocks commands (equivalent to passing --lua-version). + + Examples: + luarocks config variables.OPENSSL_DIR /usr/local/openssl + luarocks config lua_dir /usr/local + luarocks config lua_version 5.3 + +* When given a configuration key and --unset, it overwrites the config file (see + the --scope option below to determine which) and deletes that key from the + file. + + Example: luarocks config variables.OPENSSL_DIR --unset + +* When given no arguments, it prints the entire currently active configuration, + resulting from reading the config files from all scopes. + + Example: luarocks config]], util.see_also([[ + https://github.com/luarocks/luarocks/wiki/Config-file-format + for detailed information on the LuaRocks config file format. +]])): + summary("Query information about the LuaRocks configuration.") + + cmd:argument("key", "The configuration key."): + args("?") + cmd:argument("value", "The configuration value."): + args("?") + + cmd:option("--scope", "The scope indicates which config file should be rewritten.\n" .. + '* Using a wrapper created with `luarocks init`, the default is "project".\n' .. + '* Using --local (or when `local_by_default` is `true`), the default is "user".\n' .. + '* Otherwise, the default is "system".'): + choices({ "system", "user", "project" }) + cmd:flag("--unset", "Delete the key from the configuration file.") + cmd:flag("--json", "Output as JSON.") + + + cmd:flag("--lua-incdir"):hidden(true) + cmd:flag("--lua-libdir"):hidden(true) + cmd:flag("--lua-ver"):hidden(true) + cmd:flag("--system-config"):hidden(true) + cmd:flag("--user-config"):hidden(true) + cmd:flag("--rock-trees"):hidden(true) +end + +local function config_file(conf) + print(dir.normalize(conf.file)) + if conf.found then + return true + else + return nil, "file not found" + end +end + +local function traverse_varstring(var, tbl, fn, missing_parent) + local k + local r + k, r = var:match("^%[([0-9]+)%]%.(.*)$") + if k then + k = tonumber(k) + else + k, r = var:match("^([^.[]+)%.(.*)$") + if not k then + k, r = var:match("^([^[]+)(%[.*)$") + end + end + + if k then + if not tbl[k] and missing_parent then + missing_parent(tbl, k) + end + + if tbl[k] then + return traverse_varstring(r, tbl[k], fn, missing_parent) + else + return nil, "Unknown entry " .. tostring(k) + end + end + + local i = var:match("^%[([0-9]+)%]$") + if i then + return fn(tbl, tonumber(i)) + end + + return fn(tbl, var) +end + +local function print_json(value) + print(json.encode(value)) + return true +end + +local function print_entry(var, tbl, is_json) + return traverse_varstring(var, tbl, function(t, k) + if not t[k] then + return nil, "Unknown entry " .. k + end + local val = t[k] + + if not config.should_skip(var, val) then + if is_json then + return print_json(val) + elseif type(val) == "string" then + print(val) + else + persist.write_value(io.stdout, val) + end + end + return true + end) +end + +local function infer_type(var) + local typ + traverse_varstring(var, cfg, function(t, k) + if t[k] then + typ = type(t[k]) + end + end) + return typ +end + +local function write_entries(keys, scope, do_unset) + local wrote = {} + if scope == "project" and not cfg.config_files.project then + return nil, "Current directory is not part of a project. You may want to run `luarocks init`." + end + + local file_name = (cfg.config_files)[scope].file + + local tbl, err = persist.load_config_file_if_basic(file_name, cfg) + if not tbl then + return nil, err + end + + for var, val in util.sortedpairs(keys) do + traverse_varstring(var, tbl, function(t, k) + if do_unset then + t[k] = nil + wrote[var] = "" + else + local typ = infer_type(var) + local v + if typ == "number" and tonumber(val) then + v = tonumber(val) + elseif typ == "boolean" and val == "true" then + v = true + elseif typ == "boolean" and val == "false" then + v = false + else + v = val + end + t[k] = v + wrote[var] = v + end + return true + end, function(p, k) + p[k] = {} + end) + end + + local ok, err = fs.make_dir(dir.dir_name(file_name)) + if not ok then + return nil, err + end + + ok, err = persist.save_from_table(file_name, tbl) + if ok then + print(do_unset and "Removed" or "Wrote") + for var, val in util.sortedpairs(wrote) do + if do_unset then + print(("\t%s"):format(var)) + else + if type(val) == "string" then + print(("\t%s = %q"):format(var, val)) + else + print(("\t%s = %s"):format(var, tostring(val))) + end + end + end + print(do_unset and "from" or "to") + print("\t" .. file_name) + return true + else + return nil, err + end +end + +local function get_scope(args) + return args.scope or + (args["local"] and "user") or + (args.project_tree and "project") or + (cfg.local_by_default and "user") or + (fs.is_writable(cfg.config_files["system"].file) and "system") or + "user" +end + +local function report_on_lua_incdir_config(value) + local variables = { + ["LUA_DIR"] = cfg.variables.LUA_DIR, + ["LUA_BINDIR"] = cfg.variables.LUA_BINDIR, + ["LUA_INCDIR"] = value, + ["LUA_LIBDIR"] = cfg.variables.LUA_LIBDIR, + ["LUA"] = cfg.variables.LUA, + } + + local ok, err = deps.check_lua_incdir(variables) + if not ok then + util.printerr() + util.warning((err:gsub(" You can use.*", ""))) + end + return ok +end + +local function report_on_lua_libdir_config(value) + local variables = { + ["LUA_DIR"] = cfg.variables.LUA_DIR, + ["LUA_BINDIR"] = cfg.variables.LUA_BINDIR, + ["LUA_INCDIR"] = cfg.variables.LUA_INCDIR, + ["LUA_LIBDIR"] = value, + ["LUA"] = cfg.variables.LUA, + } + + local ok, err, _, err_files = deps.check_lua_libdir(variables) + if not ok then + util.printerr() + util.warning((err:gsub(" You can use.*", ""))) + util.printerr("Tried:") + for _, l in pairs(err_files or {}) do + for _, d in ipairs(l) do + util.printerr("\t" .. d) + end + end + end + return ok +end + +local function warn_bad_c_config() + util.printerr() + util.printerr("LuaRocks may not work correctly when building C modules using this configuration.") + util.printerr() +end + + + +function config_cmd.command(args) + + deps.check_lua_incdir(cfg.variables) + deps.check_lua_libdir(cfg.variables) + + + if args.lua_incdir then + print(cfg.variables.LUA_INCDIR) + return true + end + if args.lua_libdir then + print(cfg.variables.LUA_LIBDIR) + return true + end + if args.lua_ver then + print(cfg.lua_version) + return true + end + if args.system_config then + return config_file(cfg.config_files.system) + end + if args.user_config then + return config_file(cfg.config_files.user) + end + if args.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 + + if args.key == "lua_version" and args.value then + local scope = get_scope(args) + if scope == "project" and not cfg.config_files.project then + return nil, "Current directory is not part of a project. You may want to run `luarocks init`." + end + + local location = (cfg.config_files)[scope] + if (not location) or (not location.file) then + return nil, "could not get config file location for " .. tostring(scope) .. " scope" + end + + local prefix = dir.dir_name(location.file) + local ok, err = persist.save_default_lua_version(prefix, args.value) + if not ok then + return nil, "could not set default Lua version: " .. err + end + print("Lua version will default to " .. args.value .. " in " .. prefix) + end + + if args.key == "lua_dir" and args.value then + local scope = get_scope(args) + local keys = { + ["variables.LUA_DIR"] = cfg.variables.LUA_DIR, + ["variables.LUA_BINDIR"] = cfg.variables.LUA_BINDIR, + ["variables.LUA_INCDIR"] = cfg.variables.LUA_INCDIR, + ["variables.LUA_LIBDIR"] = cfg.variables.LUA_LIBDIR, + ["variables.LUA"] = cfg.variables.LUA, + } + if args.lua_version then + local prefix = dir.dir_name((cfg.config_files)[scope].file) + persist.save_default_lua_version(prefix, args.lua_version) + end + local ok, err = write_entries(keys, scope, args.unset) + if ok then + local inc_ok = report_on_lua_incdir_config(cfg.variables.LUA_INCDIR) + local lib_ok = ok and report_on_lua_libdir_config(cfg.variables.LUA_LIBDIR) + if not (inc_ok and lib_ok) then + warn_bad_c_config() + end + end + + return ok, err + end + + if args.key then + if args.key:match("^[A-Z]") then + args.key = "variables." .. args.key + end + + if args.value or args.unset then + local scope = get_scope(args) + + local ok, err = write_entries({ [args.key] = args.value or "" }, scope, args.unset) + + if ok then + if args.key == "variables.LUA_INCDIR" then + local ok = report_on_lua_incdir_config(args.value) + if not ok then + warn_bad_c_config() + end + elseif args.key == "variables.LUA_LIBDIR" then + local ok = report_on_lua_libdir_config(args.value) + if not ok then + warn_bad_c_config() + end + end + end + + return ok, err + else + return print_entry(args.key, cfg, args.json) + end + end + + if args.json then + return print_json(config.get_config_for_display(cfg)) + else + print(config.to_string(cfg)) + return true + end +end + +return config_cmd diff --git a/src/luarocks/cmd/doc.lua b/src/luarocks/cmd/doc.lua new file mode 100644 index 00000000..1389d80c --- /dev/null +++ b/src/luarocks/cmd/doc.lua @@ -0,0 +1,158 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local io = _tl_compat and _tl_compat.io or io; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local string = _tl_compat and _tl_compat.string or string + + +local doc = {} + + +local util = require("luarocks.util") +local queries = require("luarocks.queries") +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") + + + + + +function doc.add_to_parser(parser) + local cmd = parser:command("doc", "Show documentation for an installed rock.\n\n" .. + "Without any flags, tries to load the documentation using a series of heuristics.\n" .. + "With flags, return only the desired information.", util.see_also([[ + For more information about a rock, see the 'show' command. +]])): + summary("Show documentation for an installed rock.") + + cmd:argument("rock", "Name of the rock."): + action(util.namespaced_name_action) + cmd:argument("version", "Version of the rock."): + args("?") + + cmd:flag("--home", "Open the home page of project.") + cmd:flag("--list", "List documentation files only.") + cmd:flag("--porcelain", "Produce machine-friendly output.") +end + +local function show_homepage(homepage, name, namespace, version) + if not homepage then + return nil, "No 'homepage' field in rockspec for " .. util.format_rock_name(name, namespace, version) + end + util.printout("Opening " .. homepage .. " ...") + fs.browser(homepage) + return true +end + +local function try_to_open_homepage(name, namespace, 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_file("rockspec", name, namespace, 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 {} + return show_homepage(descript.homepage, name, namespace, version) +end + + + +function doc.command(args) + local query = queries.new(args.rock, args.namespace, args.version) + local iname, iversion, repo = search.pick_installed_rock(query, args.tree) + if not iname then + local rock = util.format_rock_name(args.rock, args.namespace, args.version) + util.printout(rock .. " is not installed. Looking for it in the rocks servers...") + return try_to_open_homepage(args.rock, args.namespace, args.version) + end + local 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 args.home then + return show_homepage(descript.homepage, name, args.namespace, 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 args.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) + local files = fs.find(docdir) + local htmlpatt = "%.html?$" + local extensions = { htmlpatt, "%.md$", "%.txt$", "%.textile$", "" } + local basenames = { "index", "readme", "manual" } + + local porcelain = args.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 args.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..3be0e1ec --- /dev/null +++ b/src/luarocks/cmd/download.lua @@ -0,0 +1,61 @@ + + + +local cmd_download = {} + + +local util = require("luarocks.util") +local download = require("luarocks.download") + + + + + +function cmd_download.add_to_parser(parser) + local cmd = parser:command("download", "Download a specific rock file from a rocks server.", util.see_also()) + + cmd:argument("name", "Name of the rock."): + args("?"): + action(util.namespaced_name_action) + cmd:argument("version", "Version of the rock."): + args("?") + + cmd:flag("--all", "Download all files if there are multiple matches.") + cmd:mutex( + cmd:flag("--source", "Download .src.rock if available."), + cmd:flag("--rockspec", "Download .rockspec if available."), + cmd:option("--arch", "Download rock for a specific architecture.")) + cmd:flag("--check-lua-versions", "If the rock can't be found, check repository " .. + "and report if it is available for another Lua version.") +end + + + + +function cmd_download.command(args) + if not args.name and not args.all then + return nil, "Argument missing. " .. util.see_help("download") + end + + args.name = args.name or "" + + local arch + + if args.source then + arch = "src" + elseif args.rockspec then + arch = "rockspec" + elseif args.arch then + arch = args.arch + end + + if args.all then + local ok, err = download.download_all(arch, args.name, args.namespace, args.version) + return ok, err + else + local dl, err = download.download_file(arch, args.name, args.namespace, args.version, args.check_lua_versions) + return dl and true, err + end +end + +return cmd_download diff --git a/src/luarocks/cmd/init.lua b/src/luarocks/cmd/init.lua new file mode 100644 index 00000000..9b124974 --- /dev/null +++ b/src/luarocks/cmd/init.lua @@ -0,0 +1,230 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local io = _tl_compat and _tl_compat.io or io; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table +local init = {} + + + +local cfg = require("luarocks.core.cfg") +local fs = require("luarocks.fs") +local path = require("luarocks.path") +local deps = require("luarocks.deps") +local dir = require("luarocks.dir") +local util = require("luarocks.util") +local persist = require("luarocks.persist") +local write_rockspec = require("luarocks.cmd.write_rockspec") + + + + + + + + + + +function init.add_to_parser(parser) + local cmd = parser:command("init", "Initialize a directory for a Lua project using LuaRocks.", util.see_also()) + + cmd:argument("name", "The project name."): + args("?") + cmd:argument("version", "An optional project version."): + args("?") + cmd:option("--wrapper-dir", "Location where the 'lua' and 'luarocks' wrapper scripts " .. + "should be generated; if not given, the current directory is used as a default.") + cmd:flag("--reset", "Delete any .luarocks/config-5.x.lua and ./lua and generate new ones.") + cmd:flag("--no-wrapper-scripts", "Do not generate wrapper ./lua and ./luarocks launcher scripts.") + cmd:flag("--no-gitignore", "Do not generate a .gitignore file.") + + cmd:group("Options for specifying rockspec data", write_rockspec.cmd_options(cmd)) +end + +local function gitignore_path(pwd, wrapper_dir, filename) + local norm_cur = fs.absolute_name(pwd) + local norm_file = fs.absolute_name(dir.path(wrapper_dir, filename)) + if norm_file:sub(1, #norm_cur) == norm_cur then + return norm_file:sub(#norm_cur + 2) + else + return filename + end +end + +local function write_gitignore(entries) + local gitignore = "" + local fd = io.open(".gitignore", "r") + if fd then + gitignore = fd:read("*a") + fd:close() + gitignore = "\n" .. gitignore .. "\n" + end + + fd = io.open(".gitignore", gitignore and "a" or "w") + if fd then + for _, entry in ipairs(entries) do + entry = "/" .. entry + if not gitignore:find("\n" .. entry .. "\n", 1, true) then + fd:write(entry .. "\n") + end + end + fd:close() + end +end + +local function inject_tree(tree) + path.use_tree(tree) + local tree_set = false + for _, t in ipairs(cfg.rocks_trees) do + if type(t) == "table" then + if t.name == "project" then + t.root = tree + tree_set = true + end + end + end + if not tree_set then + table.insert(cfg.rocks_trees, 1, { name = "project", root = tree }) + end +end + +local function write_wrapper_scripts(wrapper_dir, luarocks_wrapper, lua_wrapper) + local tree = dir.path(fs.current_dir(), "lua_modules") + + fs.make_dir(wrapper_dir) + + luarocks_wrapper = dir.path(wrapper_dir, luarocks_wrapper) + if not fs.exists(luarocks_wrapper) then + util.printout("Preparing " .. luarocks_wrapper .. " ...") + fs.wrap_script(arg[0], luarocks_wrapper, "none", nil, nil, "--project-tree", tree) + else + util.printout(luarocks_wrapper .. " already exists. Not overwriting it!") + end + + lua_wrapper = dir.path(wrapper_dir, lua_wrapper) + local write_lua_wrapper = true + if fs.exists(lua_wrapper) then + if not util.lua_is_wrapper(lua_wrapper) then + util.printout(lua_wrapper .. " already exists and does not look like a wrapper script. Not overwriting.") + write_lua_wrapper = false + end + end + + if write_lua_wrapper then + if util.check_lua_version(cfg.variables.LUA, cfg.lua_version) then + util.printout("Preparing " .. lua_wrapper .. " for version " .. cfg.lua_version .. "...") + + + inject_tree(tree) + + fs.wrap_script(nil, lua_wrapper, "all") + else + util.warning("No Lua interpreter detected for version " .. cfg.lua_version .. ". Not creating " .. lua_wrapper) + end + end +end + + + +function init.command(args) + local do_gitignore = not args.no_gitignore + local do_wrapper_scripts = not args.no_wrapper_scripts + local wrapper_dir = args.wrapper_dir or "." + + local pwd = fs.current_dir() + + if not args.name then + args.name = dir.base_name(pwd) + if args.name == "/" then + return nil, "When running from the root directory, please specify the argument" + end + end + + util.title("Initializing project '" .. args.name .. "' for Lua " .. cfg.lua_version .. " ...") + + local ok, err = deps.check_lua_incdir(cfg.variables) + if not ok then + return nil, err + end + + local has_rockspec = false + for file in fs.dir() do + if file:match("%.rockspec$") then + has_rockspec = true + break + end + end + + if not has_rockspec then + args.version = args.version or "dev" + args.location = pwd + local ok, err = write_rockspec.command(args) + if not ok then + util.printerr(err) + end + end + + local ext = cfg.wrapper_suffix + local luarocks_wrapper = "luarocks" .. ext + local lua_wrapper = "lua" .. ext + + if do_gitignore then + util.printout("Adding entries to .gitignore ...") + local ignores = { "lua_modules", ".luarocks" } + if do_wrapper_scripts then + table.insert(ignores, 1, gitignore_path(pwd, wrapper_dir, luarocks_wrapper)) + table.insert(ignores, 2, gitignore_path(pwd, wrapper_dir, lua_wrapper)) + end + write_gitignore(ignores) + end + + util.printout("Preparing ./.luarocks/ ...") + fs.make_dir(".luarocks") + local config_file = ".luarocks/config-" .. cfg.lua_version .. ".lua" + + if args.reset then + if do_wrapper_scripts then + fs.delete(fs.absolute_name(dir.path(wrapper_dir, lua_wrapper))) + end + fs.delete(fs.absolute_name(config_file)) + end + + local config_tbl, err = persist.load_config_file_if_basic(config_file, cfg) + if config_tbl then + local varnames = { + "LUA_DIR", + "LUA_INCDIR", + "LUA_LIBDIR", + "LUA_BINDIR", + "LUA", + } + for _, varname in ipairs(varnames) do + if cfg.variables[varname] then + config_tbl.variables = config_tbl.variables or {}; + (config_tbl.variables)[varname] = cfg.variables[varname] + end + end + local ok, err = persist.save_from_table(config_file, config_tbl) + if ok then + util.printout("Wrote " .. config_file) + else + util.printout("Failed writing " .. config_file .. ": " .. err) + end + else + util.printout("Will not attempt to overwrite " .. config_file) + end + + ok, err = persist.save_default_lua_version(".luarocks", cfg.lua_version) + if not ok then + util.printout("Failed setting default Lua version: " .. err) + end + + util.printout("Preparing ./lua_modules/ ...") + fs.make_dir("lua_modules/lib/luarocks/rocks-" .. cfg.lua_version) + + if do_wrapper_scripts then + write_wrapper_scripts(wrapper_dir, luarocks_wrapper, lua_wrapper) + end + + return true +end + +init.needs_lock = function() return true end + +return init diff --git a/src/luarocks/cmd/install.lua b/src/luarocks/cmd/install.lua new file mode 100644 index 00000000..e582f6e1 --- /dev/null +++ b/src/luarocks/cmd/install.lua @@ -0,0 +1,259 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local string = _tl_compat and _tl_compat.string or string + +local install = {} + + + +local dir = require("luarocks.dir") +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 repo_writer = require("luarocks.repo_writer") +local remove = require("luarocks.remove") +local search = require("luarocks.search") +local queries = require("luarocks.queries") +local cfg = require("luarocks.core.cfg") + + + + + + + + +function install.add_to_parser(parser) + local cmd = parser:command("install", "Install a rock.", util.see_also()) + + cmd:argument("rock", "The name of a rock to be fetched from a repository " .. + "or a filename of a locally available rock."): + action(util.namespaced_name_action) + cmd:argument("version", "Version of the rock."): + args("?") + + cmd:flag("--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.") + cmd:flag("--force", "If --keep is not specified, force removal of " .. + "previously installed versions if it would break dependencies. " .. + "If rock is already installed, reinstall it anyway.") + cmd:flag("--force-fast", "Like --force, but performs a forced removal " .. + "without reporting dependency issues.") + cmd:flag("--only-deps --deps-only", "Install only the dependencies of the rock.") + cmd:flag("--no-doc", "Install the rock without its documentation.") + cmd:flag("--verify", "Verify signature of the rockspec or src.rock being " .. + "built. If the rockspec or src.rock is being downloaded, LuaRocks will " .. + "attempt to download the signature as well. Otherwise, the signature " .. + "file should be already available locally in the same directory.\n" .. + "You need the signer’s public key in your local keyring for this " .. + "option to work properly.") + cmd:flag("--check-lua-versions", "If the rock can't be found, check repository " .. + "and report if it is available for another Lua version.") + util.deps_mode_option(cmd) + cmd:flag("--no-manifest", "Skip creating/updating the manifest") + cmd:flag("--pin", "If the installed rock is a Lua module, create a " .. + "luarocks.lock file listing the exact versions of each dependency found for " .. + "this rock (recursively), and store it in the rock's directory. " .. + "Ignores any existing luarocks.lock file in the rock's sources.") + + parser:flag("--pack-binary-rock"):hidden(true) + parser:option("--branch"):hidden(true) + parser:flag("--sign"):hidden(true) +end + + + + + + + +function install.install_binary_rock(rock_file, opts) + + local namespace = opts.namespace + local deps_mode = opts.deps_mode + + 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 + if not (opts.force or opts.force_fast) then + util.printout(name .. " " .. version .. " is already installed in " .. path.root_dir(cfg.root_dir)) + util.printout("Use --force to reinstall.") + return name, version + end + repo_writer.delete_version(name, version, opts.deps_mode) + end + + local install_dir = path.install_dir(name, version) + + local rollback = util.schedule_function(function() + fs.delete(install_dir) + fs.remove_dir_if_empty(path.versions_dir(name)) + end) + local ok + local oks, err, errcode = fetch.fetch_and_unpack_rock(rock_file, install_dir, opts.verify) + if not oks then return nil, err, errcode end + + local rockspec, err = fetch.load_rockspec(path.rockspec_file(name, version)) + if err then + return nil, "Failed loading rockspec for installed package: " .. err, errcode + end + + if opts.deps_mode ~= "none" then + ok, err, errcode = deps.check_external_deps(rockspec, "install") + if err then return nil, err, errcode end + end + + if deps_mode ~= "none" then + local deplock_dir = fs.exists(dir.path(".", "luarocks.lock")) and + "." or + install_dir + ok, err, errcode = deps.fulfill_dependencies(rockspec, "dependencies", deps_mode, opts.verify, deplock_dir) + if err then return nil, err, errcode end + end + + ok, err = repo_writer.deploy_files(name, version, repos.should_wrap_bin_scripts(rockspec), deps_mode, namespace) + if err then return nil, err end + + util.remove_scheduled_function(rollback) + rollback = util.schedule_function(function() + repo_writer.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 + + + + + + + +function install.install_binary_rock_deps(rock_file, opts) + + 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 install_dir = path.install_dir(name, version) + + local ok + local oks, err, errcode = fetch.fetch_and_unpack_rock(rock_file, install_dir, opts.verify) + if not oks then return nil, err, errcode end + + local rockspec, err = fetch.load_rockspec(path.rockspec_file(name, version)) + if err then + return nil, "Failed loading rockspec for installed package: " .. err, errcode + end + + local deplock_dir = fs.exists(dir.path(".", "luarocks.lock")) and + "." or + install_dir + ok, err, errcode = deps.fulfill_dependencies(rockspec, "dependencies", opts.deps_mode, opts.verify, deplock_dir) + if err then return nil, err, errcode end + + util.printout() + util.printout("Successfully installed dependencies for " .. name .. " " .. version) + + return name, version +end + +local function install_rock_file_deps(filename, opts) + + local name, version = install.install_binary_rock_deps(filename, opts) + if not name then return nil, version end + + deps.check_dependencies(nil, opts.deps_mode) + return true +end + +local function install_rock_file(filename, opts) + + local name, version = install.install_binary_rock(filename, opts) + if not name then return nil, version end + + if opts.no_doc then + util.remove_doc_dir(name, version) + end + + if (not opts.keep) and not cfg.keep_other_versions then + local ok, err, warn = remove.remove_other_versions(name, version, opts.force, opts.force_fast) + if not ok then + return nil, err + elseif warn then + util.printerr(err) + end + end + + deps.check_dependencies(nil, opts.deps_mode) + return true +end + + + + + + + + + +function install.command(args) + if args.rock:match("%.rockspec$") or args.rock:match("%.src%.rock$") then + local build = require("luarocks.cmd.build") + return build.command(args) + elseif args.rock:match("%.rock$") then + local deps_mode = deps.get_deps_mode(args) + local opts = { + namespace = args.namespace, + keep = not not args.keep, + force = not not args.force, + force_fast = not not args.force_fast, + no_doc = not not args.no_doc, + deps_mode = deps_mode, + verify = not not args.verify, + } + if args.only_deps then + return install_rock_file_deps(args.rock, opts) + else + return install_rock_file(args.rock, opts) + end + else + local url, err = search.find_rock_checking_lua_versions( + queries.new(args.rock, args.namespace, args.version), + args.check_lua_versions) + if not url then + return nil, err + end + util.printout("Installing " .. url) + args.rock = url + return install.command(args) + end +end + +install.needs_lock = function(args) + if args.pack_binary_rock then + return false + end + return true +end + +deps.installer = install.command + +return install diff --git a/src/luarocks/cmd/lint.lua b/src/luarocks/cmd/lint.lua new file mode 100644 index 00000000..7a8443cc --- /dev/null +++ b/src/luarocks/cmd/lint.lua @@ -0,0 +1,59 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local string = _tl_compat and _tl_compat.string or string + + +local lint = {} + + +local util = require("luarocks.util") +local download = require("luarocks.download") +local fetch = require("luarocks.fetch") + + + + + +function lint.add_to_parser(parser) + local cmd = parser:command("lint", "Check syntax of a rockspec.\n\n" .. + "Returns success if the text of the rockspec is syntactically correct, else failure.", + util.see_also()): + summary("Check syntax of a rockspec.") + + cmd:argument("rockspec", "The rockspec to check.") +end + +function lint.command(args) + + local filename = args.rockspec + if not filename:match(".rockspec$") then + local err + filename, err = download.download_file("rockspec", filename: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 + + + + + + + if not rs.description or not rs.description.license then + util.printerr("Rockspec has no description.license field.") + ok = false + end + + if ok then + return ok + end + + return nil, 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..84fb6588 --- /dev/null +++ b/src/luarocks/cmd/list.lua @@ -0,0 +1,113 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table + + +local list = {Outdated = {}, } + + + + + + + + +local search = require("luarocks.search") +local queries = require("luarocks.queries") +local vers = require("luarocks.core.vers") +local cfg = require("luarocks.core.cfg") +local util = require("luarocks.util") +local path = require("luarocks.path") + + + + + + + + + + + +function list.add_to_parser(parser) + local cmd = parser:command("list", "List currently installed rocks.", util.see_also()) + + cmd:argument("filter", "A substring of a rock name to filter by."): + args("?") + cmd:argument("version", "Rock version to filter by."): + args("?") + + cmd:flag("--outdated", "List only rocks for which there is a higher " .. + "version available in the rocks server.") + cmd:flag("--porcelain", "Produce machine-friendly output.") +end + +local function check_outdated(trees, query) + local results_installed = {} + for _, tree in ipairs(trees) do + search.local_manifest_search(results_installed, path.rocks_dir(tree), query) + end + local outdated = {} + for name, versions in util.sortedpairs(results_installed) do + local versionsk = util.keys(versions) + table.sort(versionsk, vers.compare_versions) + local latest_installed = versionsk[1] + + local query_available = queries.new(name:lower()) + local results_available = search.search_repos(query_available) + + if results_available[name] then + local available_versions = util.keys(results_available[name]) + table.sort(available_versions, vers.compare_versions) + local latest_available = available_versions[1] + local latest_available_repo = results_available[name][latest_available][1].repo + + if vers.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 + + + +function list.command(args) + local query = queries.new(args.filter and args.filter:lower() or "", args.namespace, args.version, true) + local trees = cfg.rocks_trees + local title = "Rocks installed for Lua " .. cfg.lua_version + if args.tree then + trees = { args.tree } + title = title .. " in " .. args.tree + end + + if args.outdated then + return list_outdated(trees, query, args.porcelain) + end + + local results = {} + for _, tree in ipairs(trees) do + local ok, err, errcode = search.local_manifest_search(results, path.rocks_dir(tree), query) + if not ok and errcode ~= "open" then + util.warning(err) + end + end + util.title(title, args.porcelain) + search.print_result_tree(results, args.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..e8a52906 --- /dev/null +++ b/src/luarocks/cmd/make.lua @@ -0,0 +1,179 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local string = _tl_compat and _tl_compat.string or string + + + + +local make = {} + + + +local build = require("luarocks.build") +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") +local dir = require("luarocks.dir") +local fs = require("luarocks.fs") + + + + + + + +function make.cmd_options(parser) + parser:flag("--no-install", "Do not install the rock.") + parser:flag("--no-doc", "Install the rock without its documentation.") + parser:flag("--pack-binary-rock", "Do not install rock. Instead, produce a " .. + ".rock file with the contents of compilation in the current directory.") + parser:flag("--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.") + parser:flag("--force", "If --keep is not specified, force removal of " .. + "previously installed versions if it would break dependencies. " .. + "If rock is already installed, reinstall it anyway.") + parser:flag("--force-fast", "Like --force, but performs a forced removal " .. + "without reporting dependency issues.") + parser:flag("--verify", "Verify signature of the rockspec or src.rock being " .. + "built. If the rockspec or src.rock is being downloaded, LuaRocks will " .. + "attempt to download the signature as well. Otherwise, the signature " .. + "file should be already available locally in the same directory.\n" .. + "You need the signer's public key in your local keyring for this " .. + "option to work properly.") + parser:flag("--sign", "To be used with --pack-binary-rock. Also produce a " .. + "signature file for the generated .rock file.") + parser:flag("--check-lua-versions", "If the rock can't be found, check repository " .. + "and report if it is available for another Lua version.") + parser:flag("--pin", "Pin the exact dependencies used for the rockspec" .. + "being built into a luarocks.lock file in the current directory.") + parser:flag("--no-manifest", "Skip creating/updating the manifest") + parser:flag("--only-deps --deps-only", "Install only the dependencies of the rock.") + util.deps_mode_option(parser) +end + +function make.add_to_parser(parser) + + local cmd = parser:command("make", [[ +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. + +If the current directory contains a luarocks.lock file, it is used as the +authoritative source for exact version of dependencies. The --pin flag +overrides and recreates this file scanning dependency based on ranges. +]], util.see_also()): + summary("Compile package in current directory using a rockspec.") + + + cmd:argument("rockspec", "Rockspec for the rock to build."): + args("?") + + make.cmd_options(cmd) +end + + + + +function make.command(args) + local name, namespace, version + local rockspec_filename = args.rockspec + if not rockspec_filename then + local err + rockspec_filename, err = util.get_default_rockspec() + if not rockspec_filename then + return nil, err + end + end + if not rockspec_filename:match("rockspec$") then + return nil, "Invalid argument: 'make' takes a rockspec as a parameter. " .. util.see_help("make") + end + + local cwd = fs.absolute_name(dir.path(".")) + local rockspec, err, errcode = fetch.load_rockspec(rockspec_filename) + if not rockspec then + return nil, err + end + + name, namespace = util.split_namespace(rockspec.name) + namespace = namespace or args.namespace + + local opts = { + need_to_fetch = false, + minimal_mode = true, + deps_mode = deps.get_deps_mode(args), + build_only_deps = not not (args.only_deps and not args.pack_binary_rock), + namespace = namespace, + branch = args.branch, + verify = not not args.verify, + check_lua_versions = not not args.check_lua_versions, + pin = not not args.pin, + rebuild = true, + no_install = not not args.no_install, + } + + if args.sign and not args.pack_binary_rock then + return nil, "In the make command, --sign is meant to be used only with --pack-binary-rock" + end + + if args.no_install then + name, version = build.build_rockspec(rockspec, opts, cwd) + if name then + return true + else + return nil, version + end + elseif args.pack_binary_rock then + return pack.pack_binary_rock(name, namespace, rockspec.version, args.sign, function() + name, version = build.build_rockspec(rockspec, opts, cwd) + if name and args.no_doc then + util.remove_doc_dir(name, version) + end + return name, version + end) + else + local ok, err = build.build_rockspec(rockspec, opts, cwd) + if not ok then return nil, err end + name, version = ok, err + + if opts.build_only_deps then + util.printout("Stopping after installing dependencies for " .. name .. " " .. version) + util.printout() + return name ~= nil, version + end + + if args.no_doc then + util.remove_doc_dir(name, version) + end + + if (not args.keep) and not cfg.keep_other_versions then + local ok, err, warn = remove.remove_other_versions(name, version, args.force, args.force_fast) + if not ok then + return nil, err + elseif warn then + util.printerr(warn) + end + end + + deps.check_dependencies(nil, deps.get_deps_mode(args)) + return name ~= nil, version + end +end + +make.needs_lock = function(args) + if args.pack_binary_rock or args.no_install then + return false + end + return true +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..ea70a874 --- /dev/null +++ b/src/luarocks/cmd/new_version.lua @@ -0,0 +1,237 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local math = _tl_compat and _tl_compat.math or math; local string = _tl_compat and _tl_compat.string or string + + +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 dir = require("luarocks.dir") +local type_rockspec = require("luarocks.type.rockspec") + + + + + + + + + +function new_version.add_to_parser(parser) + local cmd = parser:command("new_version", [[ +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; if the guessed +URL is invalid, the old URL is restored. 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. + +If a directory is not given, it defaults to the current directory. + +WARNING: it writes the new rockspec to the given directory, overwriting the file +if it already exists.]], util.see_also()): + summary("Auto-write a rockspec for a new version of a rock.") + + cmd:argument("rock", "Package name or rockspec."): + args("?") + cmd:argument("new_version", "New version of the rock."): + args("?") + cmd:argument("new_url", "New URL of the rock."): + args("?") + + cmd:option("--dir", "Output directory for the new rockspec.") + cmd:option("--tag", "New SCM tag.") +end + + +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 + + + + + +local function check_url_and_update_md5(out_rs, invalid_is_error) + local file, temp_dir = fetch.fetch_url_at_temp_dir(out_rs.source.url, "luarocks-new-version-" .. out_rs.package) + if not file then + if invalid_is_error then + return nil, "invalid URL - " .. temp_dir + end + util.warning("invalid URL - " .. temp_dir) + return true, false + end + do + 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 + 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 + + local old_url = out_rs.source.url + if try_replace(out_rs.source, "url", old_ver, new_ver) then + local ok, md5_changed = check_url_and_update_md5(out_rs, true) + if ok then + return ok, md5_changed + end + out_rs.source.url = old_url + end + if tag or try_replace(out_rs.source, "tag", old_ver, new_ver) then + return true + end + + 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.warning("URL is the same, but MD5 has changed. Old rockspec is broken.") + end + return true +end + +function new_version.command(args) + if not args.rock then + local err + args.rock, err = util.get_default_rockspec() + if not args.rock then + return nil, err + end + end + + local filename, err + if args.rock:match("rockspec$") then + filename, err = fetch.fetch_url(args.rock) + if not filename then + return nil, err + end + else + filename, err = download.download_file("rockspec", args.rock: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_str, new_rev + + if args.tag and not args.new_version then + args.new_version = args.tag:gsub("^v", "") + end + + local out_dir + if args.dir then + out_dir = dir.normalize(args.dir) + end + + if args.new_version then + new_ver, new_rev_str = args.new_version:match("(.*)%-(%d+)$") + new_rev = math.tointeger(new_rev_str) + if not new_rev then + new_ver = args.new_version + new_rev = 1 + end + else + new_ver = old_ver + new_rev = math.tointeger(old_rev) + 1 + end + local new_rockver = new_ver:gsub("-", "") + + local out_rs, err = persist.load_into_table(filename), string + local out_name = out_rs.package:lower() + out_rs.version = new_rockver .. "-" .. tostring(new_rev) + + local ok, err = update_source_section(out_rs, args.new_url, args.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 .. "-" .. tostring(new_rev) .. ".rockspec" + if out_dir then + out_filename = dir.path(out_dir, out_filename) + fs.make_dir(out_dir) + end + persist.save_from_table(out_filename, out_rs, type_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..7c67d9cf --- /dev/null +++ b/src/luarocks/cmd/pack.lua @@ -0,0 +1,41 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local string = _tl_compat and _tl_compat.string or string + + +local cmd_pack = {} + + +local util = require("luarocks.util") +local pack = require("luarocks.pack") +local queries = require("luarocks.queries") + + + + + +function cmd_pack.add_to_parser(parser) + local cmd = parser:command("pack", "Create a rock, packing sources or binaries.", util.see_also()) + + cmd:argument("rock", "A rockspec file, for creating a source rock, or the " .. + "name of an installed package, for creating a binary rock."): + action(util.namespaced_name_action) + cmd:argument("version", "A version may be given if the first argument is a rock name."): + args("?") + + cmd:flag("--sign", "Produce a signature file as well.") +end + + + + +function cmd_pack.command(args) + local file, err + if args.rock:match(".*%.rockspec") then + file, err = pack.pack_source_rock(args.rock) + else + local query = queries.new(args.rock, args.namespace, args.version) + file, err = pack.pack_installed_rock(query, args.tree) + end + return pack.report_and_sign_local_file(file, err, args.sign) +end + +return cmd_pack diff --git a/src/luarocks/cmd/path.lua b/src/luarocks/cmd/path.lua new file mode 100644 index 00000000..96fb3b9b --- /dev/null +++ b/src/luarocks/cmd/path.lua @@ -0,0 +1,88 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local os = _tl_compat and _tl_compat.os or os; local package = _tl_compat and _tl_compat.package or package + + +local path_cmd = {} + + +local util = require("luarocks.util") +local cfg = require("luarocks.core.cfg") +local fs = require("luarocks.fs") + + + + + +function path_cmd.add_to_parser(parser) + local cmd = parser:command("path", [[ +Returns the package path currently configured for this installation +of LuaRocks, formatted as shell commands to update LUA_PATH and LUA_CPATH. + +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"]], + util.see_also()): + summary("Return the currently configured package path.") + + cmd:flag("--no-bin", "Do not export the PATH variable.") + cmd:flag("--append", "Appends the paths to the existing paths. Default is " .. + "to prefix the LR paths to the existing paths.") + cmd:flag("--lr-path", "Prints Lua path components defined by the configured rocks trees " .. + "(not formatted as a shell command)") + cmd:flag("--lr-cpath", "Prints Lua cpath components defined by the configured rocks trees " .. + "(not formatted as a shell command)") + cmd:flag("--full", "By default, --lr-path and --lr-cpath only include the paths " .. + "derived by the LuaRocks rocks_trees. Using --full includes any other components " .. + "defined in your system's package.(c)path, either via the running interpreter's " .. + "default paths or via LUA_(C)PATH(_5_x) environment variables (in short, using " .. + "--full produces the same lists as shown in the shell outputs of 'luarocks path').") + cmd:flag("--lr-bin", "Exports the system path (not formatted as shell command).") + cmd:flag("--bin"):hidden(true) +end + + + +function path_cmd.command(args) + local lr_path, lr_cpath, lr_bin = cfg.package_paths(args.tree) + local path_sep = cfg.export_path_separator + + local full_list = ((not args.lr_path) and (not args.lr_cpath) and (not args.lr_bin)) or + args.full + + local clean_path = util.cleanup_path(os.getenv("PATH") or "", path_sep, nil, true) + + if full_list then + if args.append then + lr_path = package.path .. ";" .. lr_path + lr_cpath = package.cpath .. ";" .. lr_cpath + lr_bin = clean_path .. path_sep .. lr_bin + else + lr_path = lr_path .. ";" .. package.path + lr_cpath = lr_cpath .. ";" .. package.cpath + lr_bin = lr_bin .. path_sep .. clean_path + end + end + + if args.lr_path then + util.printout(util.cleanup_path(lr_path, ';', cfg.lua_version, true)) + return true + elseif args.lr_cpath then + util.printout(util.cleanup_path(lr_cpath, ';', cfg.lua_version, true)) + return true + elseif args.lr_bin then + util.printout(util.cleanup_path(lr_bin, path_sep, nil, true)) + return true + end + + local lpath_var, lcpath_var = util.lua_path_variables() + + util.printout(fs.export_cmd(lpath_var, util.cleanup_path(lr_path, ';', cfg.lua_version, args.append))) + util.printout(fs.export_cmd(lcpath_var, util.cleanup_path(lr_cpath, ';', cfg.lua_version, args.append))) + if not args.no_bin then + util.printout(fs.export_cmd("PATH", util.cleanup_path(lr_bin, path_sep, nil, args.append))) + 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..b7d28f82 --- /dev/null +++ b/src/luarocks/cmd/purge.lua @@ -0,0 +1,79 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local package = _tl_compat and _tl_compat.package or package + + +local purge = {} + + + +local util = require("luarocks.util") +local path = require("luarocks.path") +local search = require("luarocks.search") +local vers = require("luarocks.core.vers") +local repo_writer = require("luarocks.repo_writer") +local cfg = require("luarocks.core.cfg") +local remove = require("luarocks.remove") +local queries = require("luarocks.queries") + + + + + + +function purge.add_to_parser(parser) + + local cmd = parser:command("purge", [[ +This command removes rocks en masse from a given tree. +By default, it removes all rocks from a tree. + +The --tree option is mandatory: luarocks purge does not assume a default tree.]], + util.see_also()): + summary("Remove all installed rocks from a tree.") + + + cmd:flag("--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.") + cmd:flag("--force", "If --old-versions is specified, force removal of " .. + "previously installed versions if it would break dependencies.") + cmd:flag("--force-fast", "Like --force, but performs a forced removal " .. + "without reporting dependency issues.") +end + +function purge.command(args) + local tree = args.tree + + local results = {} + search.local_manifest_search(results, path.rocks_dir(tree), queries.all()) + + local sort = function(a, b) return vers.compare_versions(b, a) end + if args.old_versions then + sort = vers.compare_versions + end + + for package, versions in util.sortedpairs(results) do + for version, _ in util.sortedpairs(versions, sort) do + if args.old_versions then + util.printout("Keeping " .. package .. " " .. version .. "...") + local ok, err, warn = remove.remove_other_versions(package, version, args.force, args.force_fast) + if not ok then + util.printerr(err) + elseif warn then + util.printerr(err) + end + break + else + util.printout("Removing " .. package .. " " .. version .. "...") + local ok, err = repo_writer.delete_version(package, version, "none", true) + if not ok then + util.printerr(err) + end + end + end + end + return repo_writer.refresh_manifest(cfg.rocks_dir) +end + +purge.needs_lock = function() return true end + +return purge diff --git a/src/luarocks/cmd/remove.lua b/src/luarocks/cmd/remove.lua new file mode 100644 index 00000000..5a0f7dc5 --- /dev/null +++ b/src/luarocks/cmd/remove.lua @@ -0,0 +1,77 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local string = _tl_compat and _tl_compat.string or string + + +local cmd_remove = {} + + + +local remove = require("luarocks.remove") +local util = require("luarocks.util") +local cfg = require("luarocks.core.cfg") +local search = require("luarocks.search") +local path = require("luarocks.path") +local deps = require("luarocks.deps") +local queries = require("luarocks.queries") + + + + + +function cmd_remove.add_to_parser(parser) + + local cmd = parser:command("remove", [[ +Uninstall a rock. + +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 or --force-fast.]], + util.see_also()): + summary("Uninstall a rock.") + + + cmd:argument("rock", "Name of the rock to be uninstalled."): + action(util.namespaced_name_action) + cmd:argument("version", "Version of the rock to uninstall."): + args("?") + + cmd:flag("--force", "Force removal if it would break dependencies.") + cmd:flag("--force-fast", "Perform a forced removal without reporting dependency issues.") + util.deps_mode_option(cmd) +end + + + + +function cmd_remove.command(args) + local name = args.rock + local deps_mode = deps.get_deps_mode(args) + + local rock_type = name:match("%.(rock)$") or name:match("%.(rockspec)$") + local version = args.version + 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 + + name = name:lower() + + local results = {} + search.local_manifest_search(results, cfg.rocks_dir, queries.new(name, args.namespace, version)) + if not results[name] then + local rock = util.format_rock_name(name, args.namespace, version) + return nil, "Could not find rock '" .. rock .. "' in " .. path.rocks_tree_to_string(cfg.root_dir) + end + + local ok, err = remove.remove_search_results(results, name, deps_mode, args.force, args.force_fast) + if not ok then + return nil, err + end + + deps.check_dependencies(nil, deps.get_deps_mode(args)) + return true +end + +cmd_remove.needs_lock = function() return true end + +return cmd_remove diff --git a/src/luarocks/cmd/search.lua b/src/luarocks/cmd/search.lua new file mode 100644 index 00000000..3eaeda4d --- /dev/null +++ b/src/luarocks/cmd/search.lua @@ -0,0 +1,91 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local pairs = _tl_compat and _tl_compat.pairs or pairs + + +local cmd_search = {} + + +local cfg = require("luarocks.core.cfg") +local util = require("luarocks.util") +local search = require("luarocks.search") +local queries = require("luarocks.queries") +local results = require("luarocks.results") + + + + + + + +function cmd_search.add_to_parser(parser) + local cmd = parser:command("search", "Query the LuaRocks servers.", util.see_also()) + + cmd:argument("name", "Name of the rock to search for."): + args("?"): + action(util.namespaced_name_action) + cmd:argument("version", "Rock version to search for."): + args("?") + + cmd:flag("--source", "Return only rockspecs and source rocks, to be used " .. + 'with the "build" command.') + cmd:flag("--binary", "Return only pure Lua and binary rocks (rocks that " .. + 'can be used with the "install" command without requiring a C toolchain).') + cmd:flag("--all", "List all contents of the server that are suitable to " .. + "this platform, do not filter by name.") + cmd:flag("--porcelain", "Return a machine readable format.") +end + + + + + + + +local function split_source_and_binary_results(result_tree) + local sources, binaries = {}, {} + for name, versions in pairs(result_tree) 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 + local entry = results.new(name, version, repo.repo, repo.arch) + search.store_result(where, entry) + end + end + end + return sources, binaries +end + + + + +function cmd_search.command(args) + local name = args.name + + if args.all then + name, args.version = "", nil + end + + if not args.name and not args.all then + return nil, "Enter name and version or use --all. " .. util.see_help("search") + end + + local query = queries.new(name, args.namespace, args.version, true) + local result_tree = search.search_repos(query) + local porcelain = args.porcelain + local full_name = util.format_rock_name(name, args.namespace, args.version) + util.title(full_name .. " - Search results for Lua " .. cfg.lua_version .. ":", porcelain, "=") + local sources, binaries = split_source_and_binary_results(result_tree) + if next(sources) and not args.binary then + util.title("Rockspecs and source rocks:", porcelain) + search.print_result_tree(sources, porcelain) + end + if next(binaries) and not args.source then + util.title("Binary and pure-Lua rocks:", porcelain) + search.print_result_tree(binaries, porcelain) + end + return true +end + +return cmd_search diff --git a/src/luarocks/cmd/show.lua b/src/luarocks/cmd/show.lua new file mode 100644 index 00000000..58d6701e --- /dev/null +++ b/src/luarocks/cmd/show.lua @@ -0,0 +1,341 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local math = _tl_compat and _tl_compat.math or math; local os = _tl_compat and _tl_compat.os or os; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table + +local show = {Return = {}, } + + + + + + + +local queries = require("luarocks.queries") +local search = require("luarocks.search") +local dir = require("luarocks.core.dir") +local fs = require("luarocks.fs") +local cfg = require("luarocks.core.cfg") +local util = require("luarocks.util") +local path = require("luarocks.path") +local fetch = require("luarocks.fetch") +local manif = require("luarocks.manif") +local repos = require("luarocks.repos") + + + + + + + + + + + + + + + + + +function show.add_to_parser(parser) + local cmd = parser:command("show", [[ +Show information about an installed rock. + +Without any flags, show all module information. +With flags, return only the desired information.]], util.see_also()): + summary("Show information about an installed rock.") + + cmd:argument("rock", "Name of an installed rock."): + action(util.namespaced_name_action) + cmd:argument("version", "Rock version."): + args("?") + + cmd:flag("--home", "Show home page of project.") + cmd:flag("--modules", "Show all modules provided by the package as used by require().") + cmd:flag("--deps", "Show packages the package depends on.") + cmd:flag("--build-deps", "Show build-only dependencies for the package.") + cmd:flag("--test-deps", "Show dependencies for testing the package.") + cmd:flag("--rockspec", "Show the full path of the rockspec file.") + cmd:flag("--mversion", "Show the package version.") + cmd:flag("--rock-tree", "Show local tree where rock is installed.") + cmd:flag("--rock-namespace", "Show rock namespace.") + cmd:flag("--rock-dir", "Show data directory of the installed rock.") + cmd:flag("--rock-license", "Show rock license.") + cmd:flag("--issues", "Show URL for project's issue tracker.") + cmd:flag("--labels", "List the labels of the rock.") + cmd:flag("--porcelain", "Produce machine-friendly output.") +end + +local friendly_template = [[ + : +?namespace:${namespace}/${package} ${version} - ${summary} +!namespace:${package} ${version} - ${summary} + : +*detailed :${detailed} +?detailed : +?license :License: \t${license} +?homepage :Homepage: \t${homepage} +?issues :Issues: \t${issues} +?labels :Labels: \t${labels} +?location :Installed in: \t${location} +?commands : +?commands :Commands: +*commands :\t${name} (${file}) +?modules : +?modules :Modules: +*modules :\t${name} (${file}) +?bdeps : +?bdeps :Has build dependency on: +*bdeps :\t${name} (${label}) +?tdeps : +?tdeps :Tests depend on: +*tdeps :\t${name} (${label}) +?deps : +?deps :Depends on: +*deps :\t${name} (${label}) +?ideps : +?ideps :Indirectly pulling: +*ideps :\t${name} (${label}) + : +]] + +local porcelain_template = [[ +?namespace:namespace\t${namespace} +?package :package\t${package} +?version :version\t${version} +?summary :summary\t${summary} +*detailed :detailed\t${detailed} +?license :license\t${license} +?homepage :homepage\t${homepage} +?issues :issues\t${issues} +?labels :labels\t${labels} +?location :location\t${location} +*commands :command\t${name}\t${file} +*modules :module\t${name}\t${file} +*bdeps :build_dependency\t${name}\t${label} +*tdeps :test_dependency\t${name}\t${label} +*deps :dependency\t${name}\t${label} +*ideps :indirect_dependency\t${name}\t${label} +]] + +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 = math.tointeger(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(dep, tree) + local installed, version + local rocks_provided = util.get_rocks_provided() + if rocks_provided[dep.name] then + installed, version = true, rocks_provided[dep.name] + else + local name + name, version = search.pick_installed_rock(dep, tree) + installed = name ~= nil + end + return installed and "using " .. version or "missing" +end + +local function render(template, data) + local out = {} + for cmd, var, line in template:gmatch("(.)([a-z]*)%s*:([^\n]*)\n") do + line = line:gsub("\\t", "\t") + local d = data[var] + if cmd == " " then + table.insert(out, line) + elseif cmd == "?" or cmd == "*" or cmd == "!" then + if (cmd == "!" and d == nil) or + (cmd ~= "!" and (type(d) == "string" or + (type(d) == "table" and next(d)))) then + local n = type(d) == "table" and #d or 1 + if cmd ~= "*" then + n = 1 + end + for i = 1, n do + local tbl = cmd == "*" and type(d) == "table" and d[i] or data + if type(tbl) == "string" then + tbl = tbl:gsub("%%", "%%%%") + end + table.insert(out, (line:gsub("${([a-z]+)}", tbl))) + end + end + end + end + return table.concat(out, "\n") +end + +local function adjust_path(name, version, basedir, pathname, suffix) + pathname = dir.path(basedir, pathname) + local vpathname = path.versioned_name(pathname, basedir, name, version) + return (fs.exists(vpathname) and + vpathname or + pathname) .. (suffix or "") +end + +local function modules_to_list(name, version, repo) + local ret = {} + local rock_manifest = manif.load_rock_manifest(name, version, repo) + + local lua_dir = path.deploy_lua_dir(repo) + local lib_dir = path.deploy_lib_dir(repo) + repos.recurse_rock_manifest_entry(rock_manifest.lua, function(pathname) + table.insert(ret, { + name = path.path_to_module(pathname), + file = adjust_path(name, version, lua_dir, pathname), + }) + end) + repos.recurse_rock_manifest_entry(rock_manifest.lib, function(pathname) + table.insert(ret, { + name = path.path_to_module(pathname), + file = adjust_path(name, version, lib_dir, pathname), + }) + end) + table.sort(ret, function(a, b) + if a.name == b.name then + return a.file < b.file + end + return a.name < b.name + end) + return ret +end + +local function commands_to_list(name, version, repo) + local ret = {} + local rock_manifest = manif.load_rock_manifest(name, version, repo) + + local bin_dir = path.deploy_bin_dir(repo) + repos.recurse_rock_manifest_entry(rock_manifest.bin, function(pathname) + table.insert(ret, { + name = name, + file = adjust_path(name, version, bin_dir, pathname, cfg.wrapper_suffix), + }) + end) + table.sort(ret, function(a, b) + if a.name == b.name then + return a.file < b.file + end + return a.name < b.name + end) + return ret +end + +local function deps_to_list(dependencies, tree) + local ret = {} + for _, dep in ipairs(dependencies.queries or {}) do + table.insert(ret, { name = tostring(dep), label = installed_rock_label(dep, tree) }) + end + return ret +end + +local function indirect_deps(mdeps, rdeps, tree) + local ret = {} + local direct_deps = {} + for _, dep in ipairs(rdeps) do + direct_deps[dep] = true + end + for dep_name in util.sortedpairs(mdeps or {}) do + if not direct_deps[dep_name] then + table.insert(ret, { name = tostring(dep_name), label = installed_rock_label(queries.new(dep_name), tree) }) + end + end + return ret +end + +local function show_rock(template, namespace, name, version, rockspec, repo, minfo, tree) + local desc = rockspec.description or {} + local data = { + namespace = namespace, + package = rockspec.package, + version = rockspec.version, + summary = desc.summary or "", + detailed = desc.detailed and util.split_string(format_text(desc.detailed), "\n"), + license = desc.license, + homepage = desc.homepage, + issues = desc.issues_url, + labels = desc.labels and table.concat(desc.labels, ", "), + location = path.rocks_tree_to_string(repo), + commands = commands_to_list(name, version, repo), + modules = modules_to_list(name, version, repo), + bdeps = deps_to_list(rockspec.build_dependencies, tree), + tdeps = deps_to_list(rockspec.test_dependencies, tree), + deps = deps_to_list(rockspec.dependencies, tree), + ideps = indirect_deps(minfo.dependencies, rockspec.dependencies, tree), + } + util.printout(render(template, data)) +end + + + +function show.command(args) + local query = queries.new(args.rock, args.namespace, args.version, true) + + local name, version, repo, repo_url = search.pick_installed_rock(query, args.tree) + if not name then + return nil, version + end + local tree = path.rocks_tree_to_string(repo) + local directory = path.install_dir(name, version, repo) + local namespace = path.read_namespace(name, version, tree) + 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 args.rock_tree then util.printout(tree) + elseif args.rock_namespace then util.printout(namespace) + elseif args.rock_dir then util.printout(directory) + elseif args.home then util.printout(descript.homepage) + elseif args.rock_license then util.printout(descript.license) + elseif args.issues then util.printout(descript.issues_url) + elseif args.labels then util.printout(descript.labels and table.concat(descript.labels, "\n")) + elseif args.modules then util.printout(keys_as_string(minfo.modules, "\n")) + elseif args.deps then + for _, dep in ipairs(rockspec.dependencies) do + util.printout(tostring(dep)) + end + elseif args.build_deps then + for _, dep in ipairs(rockspec.build_dependencies) do + util.printout(tostring(dep)) + end + elseif args.test_deps then + for _, dep in ipairs(rockspec.test_dependencies) do + util.printout(tostring(dep)) + end + elseif args.rockspec then util.printout(rockspec_file) + elseif args.mversion then util.printout(version) + elseif args.porcelain then + show_rock(porcelain_template, namespace, name, version, rockspec, repo, minfo, args.tree) + else + show_rock(friendly_template, namespace, name, version, rockspec, repo, minfo, args.tree) + end + return true +end + +return show diff --git a/src/luarocks/cmd/test.lua b/src/luarocks/cmd/test.lua new file mode 100644 index 00000000..9305edba --- /dev/null +++ b/src/luarocks/cmd/test.lua @@ -0,0 +1,53 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table + + +local cmd_test = {} + + +local util = require("luarocks.util") +local test = require("luarocks.test") + + + + + +function cmd_test.add_to_parser(parser) + local cmd = parser:command("test", [[ +Run the test suite for the Lua project in the current directory. + +If the first argument is a rockspec, it will use it to determine the parameters +for running tests; otherwise, it will attempt to detect the rockspec. + +Any additional arguments are forwarded to the test suite. +To make sure that test suite flags are not interpreted as LuaRocks flags, use -- +to separate LuaRocks arguments from test suite arguments.]], + util.see_also()): + summary("Run the test suite in the current directory.") + + cmd:argument("rockspec", "Project rockspec."): + args("?") + cmd:argument("args", "Test suite arguments."): + args("*") + cmd:flag("--prepare", "Only install dependencies needed for testing only, but do not run the test") + + cmd:option("--test-type", "Specify the test suite type manually if it was " .. + "not specified in the rockspec and it could not be auto-detected."): + argname("") +end + +function cmd_test.command(args) + if args.rockspec and args.rockspec:match("rockspec$") then + return test.run_test_suite(args.rockspec, args.test_type, args.args, args.prepare) + end + + table.insert(args.args, 1, args.rockspec) + + local rockspec, err = util.get_default_rockspec() + if not rockspec then + return nil, err + end + + return test.run_test_suite(rockspec, args.test_type, args.args, args.prepare) +end + +return cmd_test diff --git a/src/luarocks/cmd/unpack.lua b/src/luarocks/cmd/unpack.lua new file mode 100644 index 00000000..5a1ae799 --- /dev/null +++ b/src/luarocks/cmd/unpack.lua @@ -0,0 +1,172 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local string = _tl_compat and _tl_compat.string or string + + +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 search = require("luarocks.search") + + + + + + + +function unpack.add_to_parser(parser) + local cmd = parser:command("unpack", [[ +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 rock version may be given as a second argument.]], + util.see_also()): + summary("Unpack the contents of a rock.") + + cmd:argument("rock", "A rock file or the name of a rock."): + action(util.namespaced_name_action) + cmd:argument("version", "Rock version."): + args("?") + + cmd:flag("--force", "Unpack files even if the output directory already exists.") + cmd:flag("--check-lua-versions", "If the rock can't be found, check repository " .. + "and report if it is available for another Lua version.") +end + + + + + + + +local function unpack_rockspec(rockspec_file, dir_name) + + 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 filename, sources_dir = fetch.fetch_sources(rockspec, true, ".") + if not filename 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 + + + + + + + + +local function unpack_rock(rock_file, dir_name, kind) + + local ok, filename, err, errcode + filename, err, errcode = fetch.fetch_and_unpack_rock(rock_file, dir_name) + if not filename then + return nil, err, 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 + ok, err = fs.unpack_archive(rockspec.source.file) + if not ok then return nil, err end + ok, err = fetch.find_rockspec_source_dir(rockspec, ".") + 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 + + + + + + + +local function run_unpacker(file, force) + + 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 + fetch.find_rockspec_source_dir(rockspec, ".") + if rockspec.source.dir ~= "." then + local ok = fs.copy(rockspec.local_abs_filename, rockspec.source.dir, "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 + + + + +function unpack.command(args) + local url, err + if args.rock:match(".*%.rock") or args.rock:match(".*%.rockspec") then + url = args.rock + else + url, err = search.find_src_or_rockspec(args.rock, args.namespace, args.version, args.check_lua_versions) + if not url then + return nil, err + end + end + + return run_unpacker(url, args.force) +end + +return unpack diff --git a/src/luarocks/cmd/upload.lua b/src/luarocks/cmd/upload.lua new file mode 100644 index 00000000..70a1f9b9 --- /dev/null +++ b/src/luarocks/cmd/upload.lua @@ -0,0 +1,144 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local string = _tl_compat and _tl_compat.string or string +local upload = {Response = {version = {}, }, } + + + + + + + + + + + +local signing = require("luarocks.signing") +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") + + + + + + + +function upload.add_to_parser(parser) + local cmd = parser:command("upload", "Pack a source rock file (.src.rock extension) " .. + "and upload it and the rockspec to the public rocks repository.", util.see_also()): + summary("Upload a rockspec to the public rocks repository.") + + cmd:argument("rockspec", "Rockspec for the rock to upload.") + cmd:argument("src-rock", "A corresponding .src.rock file; if not given it will be generated."): + args("?") + + cmd:flag("--skip-pack", "Do not pack and send source rock.") + cmd:option("--api-key", "Pass an API key. It will be stored for subsequent uses."): + argname("") + cmd:option("--temp-key", "Use the given a temporary API key in this " .. + "invocation only. It will not be stored."): + argname("") + cmd:flag("--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.") + cmd:flag("--sign", "Upload a signature file alongside each file as well.") + cmd:flag("--debug"):hidden(true) +end + +local function is_dev_version(version) + return version:match("^dev") or version:match("^scm") +end + +function upload.command(args) + local api, err = Api.new(args) + if not api then + return nil, err + end + if cfg.verbose then + api.debug = true + end + + local rockspec, err, errcode = fetch.load_rockspec(args.rockspec) + if err then + return nil, err, errcode + end + + util.printout("Sending " .. tostring(args.rockspec) .. " ...") + 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 args.force then + return nil, "Revision " .. rockspec.version .. " already exists on the server. " .. util.see_help("upload") + end + + local sigfname + local rock_sigfname + + if args.sign then + sigfname, err = signing.sign_file(args.rockspec) + if err then + return nil, "Failed signing rockspec: " .. err + end + util.printout("Signed rockspec: " .. sigfname) + end + + local rock_fname + if args.src_rock then + rock_fname = args.src_rock + elseif not args.skip_pack and not is_dev_version(rockspec.version) then + util.printout("Packing " .. tostring(rockspec.package)) + rock_fname, err = pack.pack_source_rock(args.rockspec) + if not rock_fname then + return nil, err + end + end + + if rock_fname and args.sign then + rock_sigfname, err = signing.sign_file(rock_fname) + if err then + return nil, "Failed signing rock: " .. err + end + util.printout("Signed packed rock: " .. rock_sigfname) + end + + local multipart = require("luarocks.upload.multipart") + + res, err = api:method("upload", nil, { + rockspec_file = multipart.new_file(args.rockspec), + rockspec_sig = sigfname and multipart.new_file(sigfname), + }) + 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), + rock_sig = rock_sigfname and multipart.new_file(rock_sigfname), + }) + 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/which.lua b/src/luarocks/cmd/which.lua new file mode 100644 index 00000000..c49d43c4 --- /dev/null +++ b/src/luarocks/cmd/which.lua @@ -0,0 +1,44 @@ + + + +local which_cmd = {} + + +local loader = require("luarocks.loader") +local cfg = require("luarocks.core.cfg") +local util = require("luarocks.util") + + + + + +function which_cmd.add_to_parser(parser) + local cmd = parser:command("which", 'Given a module name like "foo.bar", ' .. + "output which file would be loaded to resolve that module by " .. + 'luarocks.loader, like "/usr/local/lua/' .. cfg.lua_version .. '/foo/bar.lua".', + util.see_also()): + summary("Tell which file corresponds to a given module name.") + + cmd:argument("modname", "Module name.") +end + + + +function which_cmd.command(args) + local pathname, rock_name, rock_version, where = loader.which(args.modname, "lp") + + if pathname then + util.printout(pathname) + if where == "l" then + util.printout("(provided by " .. tostring(rock_name) .. " " .. tostring(rock_version) .. ")") + else + local key = rock_name + util.printout("(found directly via package." .. key .. " -- not installed as a rock?)") + end + return true + end + + return nil, "Module '" .. args.modname .. "' not found." +end + +return which_cmd diff --git a/src/luarocks/cmd/write_rockspec.lua b/src/luarocks/cmd/write_rockspec.lua new file mode 100644 index 00000000..156339b8 --- /dev/null +++ b/src/luarocks/cmd/write_rockspec.lua @@ -0,0 +1,423 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local assert = _tl_compat and _tl_compat.assert or assert; local io = _tl_compat and _tl_compat.io or io; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local pairs = _tl_compat and _tl_compat.pairs or pairs; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table +local write_rockspec = {} + + +local builtin = require("luarocks.build.builtin") +local cfg = require("luarocks.core.cfg") +local dir = require("luarocks.dir") +local fetch = require("luarocks.fetch") +local fs = require("luarocks.fs") +local persist = require("luarocks.persist") +local rockspecs = require("luarocks.rockspecs") +local type_rockspec = require("luarocks.type.rockspec") +local util = require("luarocks.util") + + + + + + + + + + + + +local lua_versions = { + "5.1", + "5.2", + "5.3", + "5.4", + "5.1,5.2", + "5.2,5.3", + "5.3,5.4", + "5.1,5.2,5.3", + "5.2,5.3,5.4", + "5.1,5.2,5.3,5.4", +} + +function write_rockspec.cmd_options(parser) + parser:option("--output", "Write the rockspec with the given filename.\n" .. + "If not given, a file is written in the current directory with a " .. + "filename based on given name and version."): + argname("") + parser:option("--license", 'A license string, such as "MIT/X11" or "GNU GPL v3".'): + argname("") + parser:option("--summary", "A short one-line description summary."): + argname("") + parser:option("--detailed", "A longer description string."): + argname("") + parser:option("--homepage", "Project homepage."): + argname("") + parser:option("--lua-versions", 'Supported Lua versions. Accepted values are: "' .. + table.concat(lua_versions, '", "') .. '".'): + argname(""): + choices(lua_versions) + parser:option("--rockspec-format", 'Rockspec format version, such as "1.0" or "1.1".'): + argname("") + parser:option("--tag", "Tag to use. Will attempt to extract version number from it.") + parser:option("--lib", "A comma-separated list of libraries that C files need to link to."): + argname("") +end + +function write_rockspec.add_to_parser(parser) + local cmd = parser:command("write_rockspec", [[ +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 the location. +LuaRocks will attempt to infer name and version if not given, +using 'dev' as a fallback default version. + +Note that the generated file is a _starting point_ for writing a +rockspec, and is not guaranteed to be complete or correct. ]], util.see_also()): + summary("Write a template for a rockspec file.") + + cmd:argument("name", "Name of the rock."): + args("?") + cmd:argument("version", "Rock version."): + args("?") + cmd:argument("location", "URL or path to the rock sources."): + args("?") + + write_rockspec.cmd_options(cmd) +end + +local function open_file(name) + return io.open(dir.path(fs.current_dir(), name), "r") +end + +local function fetch_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 dir.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 lua_version_dep = { + ["5.1"] = "lua ~> 5.1", + ["5.2"] = "lua ~> 5.2", + ["5.3"] = "lua ~> 5.3", + ["5.4"] = "lua ~> 5.4", + ["5.1,5.2"] = "lua >= 5.1, < 5.3", + ["5.2,5.3"] = "lua >= 5.2, < 5.4", + ["5.3,5.4"] = "lua >= 5.3, < 5.5", + ["5.1,5.2,5.3"] = "lua >= 5.1, < 5.4", + ["5.2,5.3,5.4"] = "lua >= 5.2, < 5.5", + ["5.1,5.2,5.3,5.4"] = "lua >= 5.1, < 5.5", +} + +local simple_scm_protocols = { + git = true, + ["git+http"] = true, + ["git+https"] = true, + ["git+ssh"] = true, + hg = true, + ["hg+http"] = true, + ["hg+https"] = true, + ["hg+ssh"] = true, +} + +local detect_url +do + 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 url:match("^[^@:/]+@[^@:/]+:.*$") then + local u, h, p = url:match("^([^@]+)@([^:]+):(.*)$") + url = program .. "+ssh://" .. u .. "@" .. h .. "/" .. p + elseif 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 + + detect_url = function(url_or_dir) + if url_or_dir:match("://") then + return url_or_dir + else + return detect_scm_url(url_or_dir) or "*** please add URL for source tarball, zip or repository here ***" + end + end +end + +local function detect_homepage(url, homepage) + if homepage then + return homepage + end + local url_protocol, url_path = dir.split_url(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 + return "https://" .. url_path:gsub("%.git$", "") + end + end + end + + return "*** please enter a project homepage ***" +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 licenses = { + [78656] = "MIT", + [49311] = "ISC", +} + +local function detect_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 licenses[sum] +end + +local function check_license() + 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 license = detect_license(data) + if license then + return license, data + end + return nil, data +end + +local function fill_as_builtin(rockspec, libs) + rockspec.build.type = "builtin" + + 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 + (rockspec.build).modules, rockspec.build.install, rockspec.build.copy_directories = builtin.autodetect_modules(libs, incdirs, libdirs) +end + +local function rockspec_cleanup(rockspec) + rockspec.source.file = nil + rockspec.source.protocol = nil + rockspec.source.identifier = nil + rockspec.source.dir = nil + rockspec.source.dir_set = nil + rockspec.source.pathname = nil + rockspec.variables = nil + rockspec.name = nil + rockspec.format_is_at_least = nil + rockspec.local_abs_filename = nil + rockspec.rocks_provided = nil + + local dep_lists = { + dependencies = rockspec.dependencies, + build_dependencies = rockspec.build_dependencies, + test_dependencies = rockspec.test_dependencies, + } + + for name, data in pairs(dep_lists) do + if not next(data) then + (rockspec)[name] = nil + else + for i, item in ipairs(data) do + data[i] = tostring(item) + end + end + end +end + +function write_rockspec.command(args) + local name, version = args.name, args.version + local location = args.location + + if not name then + location = "." + elseif not version then + location = name + name = nil + elseif not location then + location = version + version = nil + end + + if args.tag then + if not version then + version = args.tag:gsub("^v", "") + end + end + + local protocol, pathname = dir.split_url(location) + if protocol == "file" then + if pathname == "." then + name = name or dir.base_name(fs.current_dir()) + end + elseif dir.is_basic_protocol(protocol) then + local filename = dir.base_name(location) + 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(location):gsub("%.[^.]+$", "") + end + + if not name then + return nil, "Could not infer rock name. " .. util.see_help("write_rockspec") + end + version = version or "dev" + + local filename = args.output or dir.path(fs.current_dir(), name:lower() .. "-" .. version .. "-1.rockspec") + + local url = detect_url(location) + local homepage = detect_homepage(url, args.homepage) + + local rockspec, err = rockspecs.from_persisted_table(filename, { + rockspec_format = args.rockspec_format, + package = name, + version = version .. "-1", + source = { + url = url, + tag = args.tag, + }, + description = { + summary = args.summary or "*** please specify description summary ***", + detailed = args.detailed or "*** please enter a detailed description ***", + homepage = homepage, + license = args.license or "*** please specify a license ***", + }, + dependencies = { + (lua_version_dep)[args.lua_versions], + }, + build = {}, + }) + assert(not err, err) + rockspec.source.protocol = protocol + + if not next(rockspec.dependencies) then + util.warning("Please specify supported Lua versions with --lua-versions=. " .. util.see_help("write_rockspec")) + end + + local local_dir = location + + if location:match("://") then + rockspec.source.file = dir.base_name(location) + if not dir.is_basic_protocol(rockspec.source.protocol) then + if version ~= "dev" then + rockspec.source.tag = args.tag or "v" .. version + end + end + rockspec.source.dir = nil + local ok, base_dir, temp_dir = fetch_url(rockspec) + if ok then + if base_dir ~= dir.base_name(location) 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 + end + + if not local_dir then + local_dir = "." + end + + local libs = nil + if args.lib then + libs = {} + rockspec.external_dependencies = {} + for lib in args.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 (args.summary and args.detailed) then + local summary, detailed = detect_description() + rockspec.description.summary = args.summary or summary + rockspec.description.detailed = args.detailed or detailed + end + + if not args.license then + local license, fulltext = check_license() + if license then + rockspec.description.license = license + elseif license then + util.title("Could not auto-detect type for project license:") + util.printout(fulltext) + util.printout() + util.title("Please fill in the source.license field manually or use --license.") + end + end + + fill_as_builtin(rockspec, libs) + + rockspec_cleanup(rockspec) + + persist.save_from_table(filename, rockspec, type_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.lua b/src/luarocks/config.lua new file mode 100644 index 00000000..18ec90da --- /dev/null +++ b/src/luarocks/config.lua @@ -0,0 +1,38 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local pairs = _tl_compat and _tl_compat.pairs or pairs; local config = {} + +local persist = require("luarocks.persist") + +local cfg_skip = { + errorcodes = true, + flags = true, + platforms = true, + root_dir = true, + upload_servers = true, +} + + + +function config.should_skip(k, v) + return type(v) == "function" or cfg_skip[k] +end + +local function cleanup(tbl) + local copy = {} + for k, v in pairs(tbl) do + if not (type(k) == "string" and config.should_skip(k, v)) then + copy[k] = v + end + end + return copy +end + +function config.get_config_for_display(cfg) + return cleanup(cfg) +end + +function config.to_string(cfg) + local cleancfg = config.get_config_for_display(cfg) + return persist.save_from_table_to_string(cleancfg) +end + +return config diff --git a/src/luarocks/core/dir.lua b/src/luarocks/core/dir.lua new file mode 100644 index 00000000..b9b71c14 --- /dev/null +++ b/src/luarocks/core/dir.lua @@ -0,0 +1,95 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local package = _tl_compat and _tl_compat.package or package; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table; local dir = {} + + + +local dir_sep = package.config:sub(1, 1) + +local function unquote(c) + local first, last = c:sub(1, 1), c:sub(-1) + if (first == '"' and last == '"') or + (first == "'" and last == "'") then + return c:sub(2, -2) + end + return c +end + + + + + + +function dir.split_url(url) + + url = unquote(url) + local protocol, pathname = url:match("^([^:]*)://(.*)") + if not protocol then + protocol = "file" + pathname = url + end + return protocol, pathname +end + + + + + + + +function dir.normalize(name) + local protocol, pathname = dir.split_url(name) + pathname = pathname:gsub("\\", "/"):gsub("(.)/*$", "%1"):gsub("//", "/") + local pieces = {} + local drive = "" + if pathname:match("^.:") then + drive, pathname = pathname:match("^(.:)(.*)$") + end + pathname = pathname .. "/" + for piece in pathname:gmatch("(.-)/") do + if piece == ".." then + local prev = pieces[#pieces] + if not prev or prev == ".." then + table.insert(pieces, "..") + elseif prev ~= "" then + table.remove(pieces) + end + elseif piece ~= "." then + table.insert(pieces, piece) + end + end + if #pieces == 0 then + pathname = drive .. "." + elseif #pieces == 1 and pieces[1] == "" then + pathname = drive .. "/" + else + pathname = drive .. table.concat(pieces, "/") + end + if protocol ~= "file" then + pathname = protocol .. "://" .. pathname + else + pathname = pathname:gsub("/", dir_sep) + end + return pathname +end + + + + + + + + + + + +function dir.path(...) + local t = { ... } + while t[1] == "" do + table.remove(t, 1) + end + for i, c in ipairs(t) do + t[i] = unquote(c) + end + return dir.normalize(table.concat(t, "/")) +end + +return dir diff --git a/src/luarocks/core/manif.lua b/src/luarocks/core/manif.lua new file mode 100644 index 00000000..11a6cf41 --- /dev/null +++ b/src/luarocks/core/manif.lua @@ -0,0 +1,124 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local table = _tl_compat and _tl_compat.table or table + +local manif = {} + + +local persist = require("luarocks.core.persist") +local cfg = require("luarocks.core.cfg") +local dir = require("luarocks.core.dir") +local util = require("luarocks.core.util") +local vers = require("luarocks.core.vers") +local path = require("luarocks.core.path") + + + + + + + + + + + + + + +local manifest_cache = {} + + + + + +function manif.cache_manifest(repo_url, lua_version, manifest) + lua_version = lua_version or cfg.lua_version + manifest_cache[repo_url] = manifest_cache[repo_url] or {} + manifest_cache[repo_url][lua_version] = manifest +end + + + + + +function manif.get_cached_manifest(repo_url, lua_version) + lua_version = lua_version or cfg.lua_version + return manifest_cache[repo_url] and manifest_cache[repo_url][lua_version] +end + + + + + + + + +function manif.manifest_loader(file, repo_url, lua_version) + local manifest, err, errcode = persist.load_into_table(file) + if not manifest and type(err) == "string" then + return nil, "Failed loading manifest for " .. repo_url .. ": " .. err, errcode + end + + manif.cache_manifest(repo_url, lua_version, manifest) + return manifest, err, errcode +end + + + + + + +function manif.fast_load_local_manifest(repo_url) + + local cached_manifest = manif.get_cached_manifest(repo_url) + if cached_manifest then + return cached_manifest + end + + local pathname = dir.path(repo_url, "manifest") + return manif.manifest_loader(pathname, repo_url, nil) +end + +function manif.load_rocks_tree_manifests(deps_mode) + local trees = {} + path.map_trees(deps_mode, function(tree) + local manifest = manif.fast_load_local_manifest(path.rocks_dir(tree)) + if manifest then + table.insert(trees, { tree = tree, manifest = manifest }) + end + end) + return trees +end + +function manif.scan_dependencies(name, version, tree_manifests, dest) + if dest[name] then + return + end + dest[name] = version + + for _, tree in ipairs(tree_manifests) do + local manifest = tree.manifest + + local pkgdeps + if manifest.dependencies and manifest.dependencies[name] then + pkgdeps = manifest.dependencies[name][version] + end + if pkgdeps then + for _, dep in ipairs(pkgdeps) do + local pkg, constraints = dep.name, dep.constraints + + for _, t in ipairs(tree_manifests) do + local entries = t.manifest.repository[pkg] + if entries then + for ver, _ in util.sortedpairs(entries, vers.compare_versions) do + if (not constraints) or vers.match_constraints(vers.parse_version(ver), constraints) then + manif.scan_dependencies(pkg, ver, tree_manifests, dest) + end + end + end + end + end + return + end + end +end + +return manif diff --git a/src/luarocks/core/path.lua b/src/luarocks/core/path.lua new file mode 100644 index 00000000..b88b3bfc --- /dev/null +++ b/src/luarocks/core/path.lua @@ -0,0 +1,151 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local assert = _tl_compat and _tl_compat.assert or assert; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local package = _tl_compat and _tl_compat.package or package; local pairs = _tl_compat and _tl_compat.pairs or pairs; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table +local path = {} + + +local cfg = require("luarocks.core.cfg") +local dir = require("luarocks.core.dir") + + + +local dir_sep = package.config:sub(1, 1) + + +function path.rocks_dir(tree) + if tree == nil then + tree = cfg.root_dir + end + if type(tree) == "string" then + return dir.path(tree, cfg.rocks_subdir) + end + return tree.rocks_dir or dir.path(tree.root, cfg.rocks_subdir) +end + + + + + + + +function path.versioned_name(file, prefix, name, version) + assert(not name:match(dir_sep)) + + local rest = file:sub(#prefix + 1):gsub("^" .. dir_sep .. "*", "") + local name_version = (name .. "_" .. version):gsub("%-", "_"):gsub("%.", "_") + return dir.path(prefix, name_version .. "-" .. rest) +end + + + + + + + + +function path.path_to_module(file) + + local exts = {} + local paths = package.path .. ";" .. package.cpath + for entry in paths:gmatch("[^;]+") do + local ext = entry:match("%.([a-z]+)$") + if ext then + exts[ext] = true + end + end + + local name + for ext, _ in pairs(exts) do + name = file:match("(.*)%." .. ext .. "$") + if name then + name = name:gsub("[\\/]", ".") + break + end + end + + if not name then name = file end + + + name = name:gsub("^%.+", ""):gsub("%.+$", "") + + return name +end + +function path.deploy_lua_dir(tree) + if type(tree) == "string" then + return dir.path(tree, cfg.lua_modules_path) + else + return tree.lua_dir or dir.path(tree.root, cfg.lua_modules_path) + end +end + +function path.deploy_lib_dir(tree) + if type(tree) == "string" then + return dir.path(tree, cfg.lib_modules_path) + else + return tree.lib_dir or dir.path(tree.root, cfg.lib_modules_path) + end +end + +local is_src_extension = { [".lua"] = true, [".tl"] = true, [".tld"] = true, [".moon"] = true } + + + + + + + + + +function path.which_i(file_name, name, version, tree, i) + local deploy_dir + local extension = file_name:match("%.[a-z]+$") + if is_src_extension[extension] then + deploy_dir = path.deploy_lua_dir(tree) + file_name = dir.path(deploy_dir, file_name) + else + deploy_dir = path.deploy_lib_dir(tree) + file_name = dir.path(deploy_dir, file_name) + end + if i > 1 then + file_name = path.versioned_name(file_name, deploy_dir, name, version) + end + return file_name +end + +function path.rocks_tree_to_string(tree) + if type(tree) == "string" then + return tree + else + return tree.root + end +end + + + + + + + + +function path.map_trees(deps_mode, fn, ...) + local result = {} + local current = cfg.root_dir or cfg.rocks_trees[1] + if deps_mode == "one" then + table.insert(result, (fn(current, ...)) or 0) + else + local use = false + if deps_mode == "all" then + use = true + end + for _, tree in ipairs(cfg.rocks_trees or {}) do + if dir.normalize(path.rocks_tree_to_string(tree)) == dir.normalize(path.rocks_tree_to_string(current)) then + use = true + end + if use then + table.insert(result, (fn(tree, ...)) or 0) + end + end + end + return result +end + +return path diff --git a/src/luarocks/core/persist.lua b/src/luarocks/core/persist.lua new file mode 100644 index 00000000..258a42c0 --- /dev/null +++ b/src/luarocks/core/persist.lua @@ -0,0 +1,70 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local io = _tl_compat and _tl_compat.io or io; local load = _tl_compat and _tl_compat.load or load; local pcall = _tl_compat and _tl_compat.pcall or pcall; local string = _tl_compat and _tl_compat.string or string +local persist = {} + + + + + + + + + + +function persist.run_file(filename, env) + local fd, open_err = io.open(filename) + if not fd then + return nil, open_err, "open" + end + local str, read_err = fd:read("*a") + fd:close() + if not str then + return nil, read_err, "open" + end + str = str:gsub("^#![^\n]*\n", "") + local chunk, ran, err + chunk, err = load(str, filename, "t", env) + if chunk then + ran, err = pcall(chunk) + end + if not chunk then + return nil, "Error loading file: " .. tostring(err), "load" + end + if not ran then + return nil, "Error running file: " .. tostring(err), "run" + end + return true, err +end + + + + + + + + + + + +function persist.load_into_table(filename, tbl) + + local result = tbl or {} + local globals = {} + local globals_mt = { + __index = function(_, k) + globals[k] = true + end, + } + local save_mt = getmetatable(result) + setmetatable(result, globals_mt) + + local ok, err, errcode = persist.run_file(filename, result) + + setmetatable(result, save_mt) + + if not ok then + return nil, tostring(err), errcode + end + return result, globals +end + +return persist diff --git a/src/luarocks/core/sysdetect.lua b/src/luarocks/core/sysdetect.lua new file mode 100644 index 00000000..3a2527f4 --- /dev/null +++ b/src/luarocks/core/sysdetect.lua @@ -0,0 +1,508 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local assert = _tl_compat and _tl_compat.assert or assert; local io = _tl_compat and _tl_compat.io or io; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local os = _tl_compat and _tl_compat.os or os; local package = _tl_compat and _tl_compat.package or package; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table + + + + + +local sysdetect = {} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +local function hex(s) + return (s:gsub("$(..)", function(x) + return string.char(tonumber(x, 16)) + end)) +end + +local function read_int8(fd) + if io.type(fd) == "closed file" then + return nil + end + local s = fd:read(1) + if not s then + fd:close() + return nil + end + return s:byte() +end + +local function bytes2number(s, endian) + local r = 0 + if endian == "little" then + for i = #s, 1, -1 do + r = r * 256 + s:byte(i, i) + end + else + for i = 1, #s do + r = r * 256 + s:byte(i, i) + end + end + return r +end + +local function read(fd, bytes, endian) + if io.type(fd) == "closed file" then + return nil + end + local s = fd:read(bytes) + if not s then + fd:close() + return nil + end + return bytes2number(s, endian) +end + +local function read_int32le(fd) + return read(fd, 4, "little") +end + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +local endians = { + [0x01] = "little", + [0x02] = "big", +} + +local e_osabi = { + [0x00] = "sysv", + [0x01] = "hpux", + [0x02] = "netbsd", + [0x03] = "linux", + [0x04] = "hurd", + [0x06] = "solaris", + [0x07] = "aix", + [0x08] = "irix", + [0x09] = "freebsd", + [0x0c] = "openbsd", +} + +local e_machines = { + [0x02] = "sparc", + [0x03] = "x86", + [0x08] = "mips", + [0x0f] = "hppa", + [0x12] = "sparcv8", + [0x14] = "ppc", + [0x15] = "ppc64", + [0x16] = "s390", + [0x28] = "arm", + [0x2a] = "superh", + [0x2b] = "sparcv9", + [0x32] = "ia_64", + [0x3E] = "x86_64", + [0xB6] = "alpha", + [0xB7] = "aarch64", + [0xF3] = "riscv64", + [0x9026] = "alpha", +} + +local SHT_NOTE = 7 + +local function read_elf_section_headers(fd, hdr) + local endian = endians[hdr.endian] + local word = hdr.word + + local strtab_offset + local sections = {} + local secarray = {} + for i = 0, hdr.e_shnum - 1 do + fd:seek("set", hdr.e_shoff + (i * hdr.e_shentsize)) + local section = {} + section.sh_name_off = read(fd, 4, endian) + section.sh_type = read(fd, 4, endian) + section.sh_flags = read(fd, word, endian) + section.sh_addr = read(fd, word, endian) + section.sh_offset = read(fd, word, endian) + section.sh_size = read(fd, word, endian) + section.sh_link = read(fd, 4, endian) + section.sh_info = read(fd, 4, endian) + if section.sh_type == SHT_NOTE then + fd:seek("set", section.sh_offset) + section.namesz = read(fd, 4, endian) + section.descsz = read(fd, 4, endian) + section.type = read(fd, 4, endian) + section.namedata = fd:read(section.namesz):gsub("%z.*", "") + section.descdata = fd:read(section.descsz) + elseif i == hdr.e_shstrndx then + strtab_offset = section.sh_offset + end + table.insert(secarray, section) + end + if strtab_offset then + for _, section in ipairs(secarray) do + fd:seek("set", strtab_offset + section.sh_name_off) + section.name = fd:read(32):gsub("%z.*", "") + sections[section.name] = section + end + end + return sections +end + +local function detect_elf_system(fd, hdr, sections) + local system = e_osabi[hdr.osabi] + local endian = endians[hdr.endian] + + if system == "sysv" then + local abitag = sections[".note.ABI-tag"] + if abitag then + if abitag.namedata == "GNU" and abitag.type == 1 and + abitag.descdata:sub(0, 4) == "\0\0\0\0" then + return "linux" + end + elseif sections[".SUNW_version"] or + sections[".SUNW_signature"] then + return "solaris" + elseif sections[".note.netbsd.ident"] then + return "netbsd" + elseif sections[".note.openbsd.ident"] then + return "openbsd" + elseif sections[".note.tag"] and + sections[".note.tag"].namedata == "DragonFly" then + return "dragonfly" + end + + local gnu_version_r = sections[".gnu.version_r"] + if gnu_version_r then + + local dynstr = sections[".dynstr"].sh_offset + + local idx = 0 + for _ = 0, gnu_version_r.sh_info - 1 do + fd:seek("set", gnu_version_r.sh_offset + idx) + assert(read(fd, 2, endian)) + local vn_cnt = read(fd, 2, endian) + local vn_file = read(fd, 4, endian) + local vn_next = read(fd, 2, endian) + + fd:seek("set", dynstr + vn_file) + local libname = fd:read(64):gsub("%z.*", "") + + if hdr.e_type == 0x03 and libname == "libroot.so" then + return "haiku" + elseif libname:match("linux") then + return "linux" + end + + idx = idx + (vn_next * (vn_cnt + 1)) + end + end + + local procfile = io.open("/proc/sys/kernel/ostype") + if procfile then + local version = procfile:read(6) + procfile:close() + if version == "Linux\n" then + return "linux" + end + end + end + + return system +end + +local function read_elf_header(fd) + local hdr = {} + + hdr.bits = read_int8(fd) + hdr.endian = read_int8(fd) + hdr.elf_version = read_int8(fd) + if hdr.elf_version ~= 1 then + return nil + end + hdr.osabi = read_int8(fd) + if not hdr.osabi then + return nil + end + + local endian = endians[hdr.endian] + fd:seek("set", 0x10) + hdr.e_type = read(fd, 2, endian) + local machine = read(fd, 2, endian) + local processor = e_machines[machine] or "unknown" + if endian == "little" and processor == "ppc64" then + processor = "ppc64le" + end + + local elfversion = read(fd, 4, endian) + if elfversion ~= 1 then + return nil + end + + local word = (hdr.bits == 1) and 4 or 8 + hdr.word = word + + hdr.e_entry = read(fd, word, endian) + hdr.e_phoff = read(fd, word, endian) + hdr.e_shoff = read(fd, word, endian) + hdr.e_flags = read(fd, 4, endian) + hdr.e_ehsize = read(fd, 2, endian) + hdr.e_phentsize = read(fd, 2, endian) + hdr.e_phnum = read(fd, 2, endian) + hdr.e_shentsize = read(fd, 2, endian) + hdr.e_shnum = read(fd, 2, endian) + hdr.e_shstrndx = read(fd, 2, endian) + + return hdr, processor +end + +local function detect_elf(fd) + local hdr, processor = read_elf_header(fd) + if not hdr then + return nil + end + local sections = read_elf_section_headers(fd, hdr) + local system = detect_elf_system(fd, hdr, sections) + return system, processor +end + + + + + +local mach_l64 = { + [7] = "x86_64", + [12] = "aarch64", +} + +local mach_b64 = { + [0] = "ppc64", +} + +local mach_l32 = { + [7] = "x86", + [12] = "arm", +} + +local mach_b32 = { + [0] = "ppc", +} + +local function detect_mach(magic, fd) + if not magic then + return nil + end + + if magic == hex("$CA$FE$BA$BE") then + + fd:seek("set", 0x12) + local offs = read_int8(fd) + if not offs then + return nil + end + fd:seek("set", offs * 256) + magic = fd:read(4) + return detect_mach(magic, fd) + end + + local cputype = read_int8(fd) + + if magic == hex("$CF$FA$ED$FE") then + return "macosx", mach_l64[cputype] or "unknown" + elseif magic == hex("$FE$ED$CF$FA") then + return "macosx", mach_b64[cputype] or "unknown" + elseif magic == hex("$CE$FA$ED$FE") then + return "macosx", mach_l32[cputype] or "unknown" + elseif magic == hex("$FE$ED$FA$CE") then + return "macosx", mach_b32[cputype] or "unknown" + end +end + + + + + +local pe_machine = { + [0x8664] = "x86_64", + [0x01c0] = "arm", + [0x01c4] = "armv7l", + [0xaa64] = "arm64", + [0x014c] = "x86", +} + +local function detect_pe(fd) + fd:seek("set", 60) + local peoffset = read_int32le(fd) + if not peoffset then + return nil + end + local system = "windows" + fd:seek("set", peoffset + 4) + local machine = read(fd, 2, "little") + local processor = pe_machine[machine] + + local rdata_pos_s = fd:read(736):match(".rdata%z%z............(....)") + if rdata_pos_s then + local rdata_pos = bytes2number(rdata_pos_s, "little") + fd:seek("set", rdata_pos) + local data = fd:read(512) + if data:match("cygwin") or data:match("cyggcc") then + system = "cygwin" + end + end + + return system, processor or "unknown" +end + + + + + +function sysdetect.detect_file(file) + local fd = io.open(file, "rb") + if not fd then + return nil + end + local magic = fd:read(4) + if magic == hex("$7FELF") then + return detect_elf(fd) + end + if magic == hex("MZ$90$00") then + return detect_pe(fd) + end + return detect_mach(magic, fd) +end + +local cache_system +local cache_processor + +function sysdetect.detect(input_file) + local dirsep = package.config:sub(1, 1) + local files + + if input_file then + files = { input_file } + else + if cache_system then + return cache_system, cache_processor + end + + local PATHsep + local interp = arg and arg[-1] + if dirsep == "/" then + + files = { + "/bin/sh", + "/proc/self/exe", + } + PATHsep = ":" + else + + local systemroot = os.getenv("SystemRoot") + files = { + systemroot .. "\\system32\\notepad.exe", + systemroot .. "\\explorer.exe", + } + if interp and not interp:lower():match("exe$") then + interp = interp .. ".exe" + end + PATHsep = ";" + end + if interp then + if interp:match(dirsep) then + + table.insert(files, 1, interp) + else + for d in (os.getenv("PATH") or ""):gmatch("[^" .. PATHsep .. "]+") do + table.insert(files, d .. dirsep .. interp) + end + end + end + end + for _, f in ipairs(files) do + local system, processor = sysdetect.detect_file(f) + if system then + cache_system = system + cache_processor = processor + return system, processor + end + end +end + +return sysdetect diff --git a/src/luarocks/core/types/query.lua b/src/luarocks/core/types/query.lua new file mode 100644 index 00000000..e8f318fe --- /dev/null +++ b/src/luarocks/core/types/query.lua @@ -0,0 +1,13 @@ + + +local query = {Query = {}, } + + + + + + + + + +return query diff --git a/src/luarocks/core/types/result.lua b/src/luarocks/core/types/result.lua new file mode 100644 index 00000000..974bd23a --- /dev/null +++ b/src/luarocks/core/types/result.lua @@ -0,0 +1,14 @@ + + +local result = {Result = {}, } + + + + + + + + + + +return result diff --git a/src/luarocks/core/types/rockspec.lua b/src/luarocks/core/types/rockspec.lua new file mode 100644 index 00000000..5d90fbf1 --- /dev/null +++ b/src/luarocks/core/types/rockspec.lua @@ -0,0 +1,78 @@ + + + + +local rockspec = {Description = {}, Source = {}, Test = {}, Dependencies = {}, Hooks = {}, Deploy = {}, Rockspec = {}, } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +return rockspec diff --git a/src/luarocks/core/util.lua b/src/luarocks/core/util.lua new file mode 100644 index 00000000..6a457c54 --- /dev/null +++ b/src/luarocks/core/util.lua @@ -0,0 +1,334 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local debug = _tl_compat and _tl_compat.debug or debug; local io = _tl_compat and _tl_compat.io or io; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local math = _tl_compat and _tl_compat.math or math; local os = _tl_compat and _tl_compat.os or os; local package = _tl_compat and _tl_compat.package or package; local pairs = _tl_compat and _tl_compat.pairs or pairs; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table +local util = {} + + + + + + + +local dir_sep = package.config:sub(1, 1) + + + + + + + + +function util.popen_read(cmd, spec) + local tmpfile = (dir_sep == "\\") and + (os.getenv("TMP") .. "/luarocks-" .. tostring(math.floor(math.random() * 10000))) or + os.tmpname() + os.execute(cmd .. " > " .. tmpfile) + local fd = io.open(tmpfile, "rb") + if not fd then + os.remove(tmpfile) + return "" + end + local out = fd:read(spec or "*l") + fd:close() + os.remove(tmpfile) + return out or "" +end + + + + + + + + + + + + + + + + + + +function util.show_table(t, tname, top_indent) + local cart + local autoref + + local function is_empty_table(tbl) return next(tbl) == nil end + + local function basic_serialize(o) + local so = tostring(o) + if type(o) == "function" then + local info = debug and debug.getinfo(o, "S") + if not info then + return ("%q"):format(so) + end + + if info.what == "C" then + return ("%q"):format(so .. ", C function") + else + + return ("%q"):format(so .. ", defined in (" .. info.linedefined .. "-" .. info.lastlinedefined .. ")" .. info.source) + end + elseif type(o) == "number" then + return so + else + return ("%q"):format(so) + end + end + + local function add_to_cart(value, name, indent, saved, field) + indent = indent or "" + saved = saved or {} + field = field or name + + cart = cart .. indent .. field + + if not (type(value) == "table") then + cart = cart .. " = " .. basic_serialize(value) .. ";\n" + else + if saved[value] then + cart = cart .. " = {}; -- " .. saved[value] .. " (self reference)\n" + autoref = autoref .. name .. " = " .. saved[value] .. ";\n" + else + saved[value] = name + if is_empty_table(value) then + cart = cart .. " = {};\n" + else + cart = cart .. " = {\n" + for k, v in pairs(value) do + k = basic_serialize(k) + local fname = ("%s[%s]"):format(name, k) + field = ("[%s]"):format(k) + + add_to_cart(v, fname, indent .. " ", saved, field) + end + cart = cart .. indent .. "};\n" + end + end + end + end + + tname = tname or "__unnamed__" + if not (type(t) == "table") then + return tname .. " = " .. basic_serialize(t) + end + cart, autoref = "", "" + add_to_cart(t, tname, top_indent) + return cart .. autoref +end + + + + + + +function util.matchquote(s) + return (s:gsub("[?%-+*%[%].%%()$^]", "%%%1")) +end + + + + + +function util.deep_merge(dst, src) + for k, v in pairs(src) do + if type(v) == "table" then + local dstk = dst[k] + if dstk == nil then + dst[k] = {} + dstk = dst[k] + end + if type(dstk) == "table" then + util.deep_merge(dstk, v) + else + dst[k] = v + end + else + dst[k] = v + end + end +end + + + + + +function util.deep_merge_under(dst, src) + for k, v in pairs(src) do + if type(v) == "table" then + local dstk = dst[k] + if dstk == nil then + dst[k] = {} + dstk = dst[k] + end + if type(dstk) == "table" then + util.deep_merge_under(dstk, v) + end + elseif dst[k] == nil then + dst[k] = v + end + end +end + + + +function util.split_string(str, delim, maxNb) + + if string.find(str, delim) == nil then + return { str } + end + if maxNb == nil or maxNb < 1 then + maxNb = 0 + end + local result = {} + local pat = "(.-)" .. delim .. "()" + local nb = 0 + local lastPos + for part, pos in string.gmatch(str, pat) do + nb = nb + 1 + result[nb] = part + lastPos = tonumber(pos) + if nb == maxNb then break end + end + + if nb ~= maxNb then + result[nb + 1] = string.sub(str, lastPos) + end + return result +end + + + + + + + + + + + +function util.cleanup_path(list, sep, lua_version, keep_first) + + list = list:gsub(dir_sep, "/") + + local parts = util.split_string(list, sep) + local final, entries = {}, {} + local start, stop, step + + if keep_first then + start, stop, step = 1, #parts, 1 + else + start, stop, step = #parts, 1, -1 + end + + for i = start, stop, step do + local part = parts[i]:gsub("//", "/") + if lua_version then + part = part:gsub("/lua/([%d.]+)/", function(part_version) + if part_version:sub(1, #lua_version) ~= lua_version then + return "/lua/" .. lua_version .. "/" + end + end) + end + if not entries[part] then + local at = keep_first and #final + 1 or 1 + table.insert(final, at, part) + entries[part] = true + end + end + + return (table.concat(final, sep):gsub("/", dir_sep)) +end + + + + +function util.keys(tbl) + local ks = {} + for k, _ in pairs(tbl) do + table.insert(ks, k) + end + return ks +end + + +function util.printerr(...) + io.stderr:write(table.concat({ ... }, "\t")) + io.stderr:write("\n") +end + + + +function util.warning(msg) + util.printerr("Warning: " .. msg) +end + + +local function default_sort(a, b) + local ta = type(a) + local tb = type(b) + if ta == "number" and tb == "number" then + return tonumber(a) < tonumber(b) + elseif ta == "number" then + return true + elseif tb == "number" then + return false + else + return tostring(a) < tostring(b) + end +end + + + + + + + + + + + +function util.sortedpairs(tbl, sort_by) + local keys = util.keys(tbl) + local sub_orders = nil + + if sort_by == nil then + table.sort(keys, default_sort) + elseif type(sort_by) == "function" then + table.sort(keys, sort_by) + else + + + sub_orders = sort_by.sub_orders + + local seen_ordered_key = {} + + local my_ordered_keys = {} + + for _, key in ipairs(sort_by) do + if tbl[key] then + seen_ordered_key[key] = true + table.insert(my_ordered_keys, key) + end + end + + table.sort(keys, default_sort) + + for _, key in ipairs(keys) do + if not seen_ordered_key[key] then + table.insert(my_ordered_keys, key) + end + end + + keys = my_ordered_keys + end + + local i = 1 + return function() + local key = keys[i] + i = i + 1 + return key, tbl[key], sub_orders and sub_orders[key] + end +end + +return util diff --git a/src/luarocks/core/vers.lua b/src/luarocks/core/vers.lua new file mode 100644 index 00000000..4ffb8c0d --- /dev/null +++ b/src/luarocks/core/vers.lua @@ -0,0 +1,211 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local math = _tl_compat and _tl_compat.math or math; local string = _tl_compat and _tl_compat.string or string; local vers = {} + + +local util = require("luarocks.core.util") + + + + +local deltas = { + dev = 120000000, + scm = 110000000, + cvs = 100000000, + rc = -1000, + pre = -10000, + beta = -100000, + alpha = -1000000, +} + +local version_mt = { + + + + + + + + __eq = function(v1, v2) + if #v1 ~= #v2 then + return false + end + for i = 1, #v1 do + if v1[i] ~= v2[i] then + return false + end + end + if v1.revision and v2.revision then + return (v1.revision == v2.revision) + end + return true + end, + + + + + + + + __lt = function(v1, v2) + for i = 1, math.max(#v1, #v2) do + local v1i, v2i = v1[i] or 0, v2[i] or 0 + if v1i ~= v2i then + return (v1i < v2i) + end + end + if v1.revision and v2.revision then + return (v1.revision < v2.revision) + end + return false + end, + + + + __le = function(v1, v2) + return not (v2 < v1) + end, + + + + __tostring = function(v) + return v.string + end, +} + +local version_cache = {} +setmetatable(version_cache, { + __mode = "kv", +}) + + + + + + + + + + + + +function vers.parse_version(vstring) + if not vstring then return nil end + + local cached = version_cache[vstring] + if cached then + return cached + end + + local version = {} + local i = 1 + + local function add_token(number) + version[i] = version[i] and version[i] + number / 100000 or number + i = i + 1 + end + + + local v = vstring:match("^%s*(.*)%s*$") + version.string = v + + local main, revision = v:match("(.*)%-(%d+)$") + if revision then + v = main + version.revision = tonumber(revision) + end + while #v > 0 do + + local token, rest = v:match("^(%d+)[%.%-%_]*(.*)") + if token then + add_token(tonumber(token)) + else + + token, rest = v:match("^(%a+)[%.%-%_]*(.*)") + if not token then + util.warning("version number '" .. v .. "' could not be parsed.") + version[i] = 0 + break + end + version[i] = deltas[token] or (token:byte() / 1000) + end + v = rest + end + setmetatable(version, version_mt) + version_cache[vstring] = version + return version +end + + + + + +function vers.compare_versions(a, b) + if a == b then + return false + end + return vers.parse_version(b) < vers.parse_version(a) +end + + + + + + + + + + + + + +local function partial_match(input_version, input_requested) + + local version, requested + + if not (type(input_version) == "table") then version = vers.parse_version(input_version) + else version = input_version end + if not (type(input_requested) == "table") then requested = vers.parse_version(input_requested) + else requested = input_requested end + if not (type(version) == "table") or not (type(requested) == "table") then return false end + + for i, ri in ipairs(requested) do + local vi = version[i] or 0 + if ri ~= vi then return false end + end + if requested.revision then + return requested.revision == version.revision + end + return true +end + + + + + + +function vers.match_constraints(version, constraints) + local ok = true + setmetatable(version, version_mt) + for _, constr in ipairs(constraints) do + local constr_version, constr_op = constr.version, constr.op + local cv + if type(constr_version) == "string" then + cv = vers.parse_version(constr_version) + constr.version = cv + else + cv = constr_version + end + setmetatable(cv, version_mt) + if constr_op == "==" then ok = version == cv + elseif constr_op == "~=" then ok = version ~= cv + elseif constr_op == ">" then ok = cv < version + elseif constr_op == "<" then ok = version < cv + elseif constr_op == ">=" then ok = cv <= version + elseif constr_op == "<=" then ok = version <= cv + elseif constr_op == "~>" then ok = partial_match(version, cv) + end + if not ok then break end + end + return ok +end + +return vers diff --git a/src/luarocks/deplocks.lua b/src/luarocks/deplocks.lua new file mode 100644 index 00000000..8a21ef1b --- /dev/null +++ b/src/luarocks/deplocks.lua @@ -0,0 +1,111 @@ +local deplocks = {} + +local fs = require("luarocks.fs") +local dir = require("luarocks.dir") +local util = require("luarocks.util") +local persist = require("luarocks.persist") + + + +local deptable = {} +local deptable_mode = "start" +local deplock_abs_filename +local deplock_root_rock_name + +function deplocks.init(root_rock_name, dirname) + if deptable_mode ~= "start" then + return + end + deptable_mode = "create" + + local filename = dir.path(dirname, "luarocks.lock") + deplock_abs_filename = fs.absolute_name(filename) + deplock_root_rock_name = root_rock_name + + deptable = {} +end + +function deplocks.get_abs_filename(root_rock_name) + if root_rock_name == deplock_root_rock_name then + return deplock_abs_filename + end +end + +function deplocks.load(root_rock_name, dirname) + if deptable_mode ~= "start" then + return true, nil + end + deptable_mode = "locked" + + local filename = dir.path(dirname, "luarocks.lock") + local _, result, errcode = persist.run_file(filename, {}) + if errcode == "load" or errcode == "run" then + + return nil, nil, "Could not read existing lockfile " .. filename + end + + if errcode == "open" then + + return true, nil + end + + deplock_abs_filename = fs.absolute_name(filename) + deplock_root_rock_name = root_rock_name + + deptable = result + return true, filename +end + +function deplocks.add(depskey, name, version) + if deptable_mode == "locked" then + return + end + + local dk = deptable[depskey] + if not dk then + dk = {} + deptable[depskey] = dk + end + + if type(dk) == "table" and not dk[name] then + dk[name] = version + end +end + +function deplocks.get(depskey, name) + local dk = deptable[depskey] + if not dk then + return nil + end + if type(dk) == "table" then + return dk[name] + else + return dk + end +end + +function deplocks.write_file() + if deptable_mode ~= "create" then + return true + end + + return persist.save_as_module(deplock_abs_filename, deptable) +end + + +function deplocks.proxy(depskey) + return setmetatable({}, { + __index = function(_, k) + return deplocks.get(depskey, k) + end, + __newindex = function(_, k, v) + return deplocks.add(depskey, k, v) + end, + }) +end + +function deplocks.each(depskey) + return util.sortedpairs(deptable[depskey] or {}) +end + +return deplocks diff --git a/src/luarocks/deps.lua b/src/luarocks/deps.lua new file mode 100644 index 00000000..c960f3b7 --- /dev/null +++ b/src/luarocks/deps.lua @@ -0,0 +1,877 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local assert = _tl_compat and _tl_compat.assert or assert; local io = _tl_compat and _tl_compat.io or io; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local pairs = _tl_compat and _tl_compat.pairs or pairs; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table + +local deps = {} + + + +local cfg = require("luarocks.core.cfg") +local manif = require("luarocks.manif") +local path = require("luarocks.path") +local dir = require("luarocks.dir") +local fun = require("luarocks.fun") +local util = require("luarocks.util") +local vers = require("luarocks.core.vers") +local queries = require("luarocks.queries") +local deplocks = require("luarocks.deplocks") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +local function prepare_get_versions(deps_mode, rocks_provided, depskey, skip_set) + + return function(dep) + local versions, locations + local provided = rocks_provided[dep.name] + if provided then + + versions, locations = { provided }, {} + else + if deps_mode == "none" then + deps_mode = "one" + end + versions, locations = manif.get_versions(dep, deps_mode) + end + + if skip_set and skip_set[dep.name] then + for i = #versions, 1, -1 do + local v = versions[i] + if skip_set[dep.name][v] then + table.remove(versions, i) + end + end + end + + local lockversion = deplocks.get(depskey, dep.name) + + return versions, locations, lockversion, provided ~= nil + end +end + + + + + + + + + + + + + +local function match_dep(depq, + get_versions) + + local versions, locations, lockversion, provided = get_versions(depq) + + local latest_version + local latest_vstring + for _, vstring in ipairs(versions) do + local version = vers.parse_version(vstring) + if vers.match_constraints(version, depq.constraints) then + if not latest_version or version > latest_version then + latest_version = version + latest_vstring = vstring + end + end + end + + if lockversion and not locations[lockversion] then + local latest_matching_msg = "" + if latest_vstring and latest_vstring ~= lockversion then + latest_matching_msg = " (latest matching is " .. latest_vstring .. ")" + end + util.printout("Forcing " .. depq.name .. " to pinned version " .. lockversion .. latest_matching_msg) + return nil, nil, queries.new(depq.name, depq.namespace, lockversion) + end + + return latest_vstring, locations[latest_vstring], depq, provided +end + +local function match_all_deps(dependencies, + get_versions) + + local matched, missing, no_upgrade = {}, {}, {} + + for _, depq in ipairs(dependencies) do + local found, _, provided + found, _, depq, provided = match_dep(depq, get_versions) + if found then + if not provided then + matched[depq] = { name = depq.name, version = found } + end + else + if depq.constraints and depq.constraints[1] and depq.constraints[1].no_upgrade then + no_upgrade[depq.name] = depq + else + missing[depq.name] = depq + end + end + end + return matched, missing, no_upgrade +end + + + + + + + + + + + + + + +function deps.match_deps(dependencies, rocks_provided, deps_mode, skip_set) + + local get_versions = prepare_get_versions(deps_mode, rocks_provided, "dependencies", skip_set) + return match_all_deps(dependencies, get_versions) +end + +local function rock_status(dep, get_versions) + local installed, _, _, provided = match_dep(dep, get_versions) + local installation_type = provided and "provided by VM" or "installed" + return installed and installed .. " " .. installation_type .. ": success" or "not installed" +end + + + + + + + + + + +function deps.report_missing_dependencies(name, version, dependencies, deps_mode, rocks_provided) + + if deps_mode == "none" then + return + end + + local get_versions = prepare_get_versions(deps_mode, rocks_provided, "dependencies") + + local first_missing_dep = true + + for _, depq in ipairs(dependencies) do + local found, _ + found, _, depq = match_dep(depq, get_versions) + if not found then + if first_missing_dep then + util.printout(("Missing dependencies for %s %s:"):format(name, version)) + first_missing_dep = false + end + + util.printout((" %s (%s)"):format(tostring(depq), rock_status(depq, get_versions))) + end + end +end + +function deps.fulfill_dependency(dep, deps_mode, rocks_provided, verify, depskey) + + deps_mode = deps_mode or "all" + rocks_provided = rocks_provided or {} + + local get_versions = prepare_get_versions(deps_mode, rocks_provided, depskey) + + local found, where + found, where, dep = match_dep(dep, get_versions) + if found then + local tree_manifests = manif.load_rocks_tree_manifests(deps_mode) + manif.scan_dependencies(dep.name, found, tree_manifests, deplocks.proxy(depskey)) + return true, found, where + end + + local search = require("luarocks.search") + + local url, search_err = search.find_suitable_rock(dep) + if not url then + return nil, "Could not satisfy dependency " .. tostring(dep) .. ": " .. search_err + end + util.printout("Installing " .. url) + local install_args = { + rock = url, + deps_mode = deps_mode, + namespace = dep.namespace, + verify = verify, + } + local ok, install_err, errcode = deps.installer(install_args) + if not ok then + return nil, "Failed installing dependency: " .. url .. " - " .. install_err, errcode + end + + found, where = match_dep(dep, get_versions) + if not found then + return nil, "Repository inconsistency detected (previously unfinished/corrupted installation?)" + end + return true, found, where +end + +local function check_supported_platforms(rockspec) + if rockspec.supported_platforms and next(rockspec.supported_platforms) then + local all_negative = true + local supported = false + for _, plat in ipairs(rockspec.supported_platforms) do + local neg + neg, plat = plat:match("^(!?)(.*)") + if neg == "!" then + if cfg.is_platform(plat) then + return nil, "This rockspec for " .. rockspec.package .. " does not support " .. plat .. " platforms." + end + else + all_negative = false + if cfg.is_platform(plat) then + supported = true + break + end + end + end + if supported == false and not all_negative then + local plats = cfg.print_platforms() + return nil, "This rockspec for " .. rockspec.package .. " does not support " .. plats .. " platforms." + end + end + + return true +end + + + + + + + + + + + + + +function deps.fulfill_dependencies(rockspec, depskey, deps_mode, verify, deplock_dir) + local name = rockspec.name + local version = rockspec.version + local rocks_provided = rockspec.rocks_provided + + local ok, filename, err = deplocks.load(name, deplock_dir or ".") + if filename then + util.printout("Using dependencies pinned in lockfile: " .. filename) + + local get_versions = prepare_get_versions("none", rocks_provided, depskey) + local dnsnamestr, dversionstr + for dnsname, dversion in deplocks.each(depskey) do + if type(dnsname) == "string" then + dnsnamestr = dnsname + end + if type(dversion) == "string" then + dversionstr = dversion + end + local dname, dnamespace = util.split_namespace(dnsnamestr) + local depq = queries.new(dname, dnamespace, dversionstr) + + util.printout(("%s %s is pinned to %s (%s)"):format( + name, version, tostring(depq), rock_status(depq, get_versions))) + + local okfullfill, errfullfill = deps.fulfill_dependency(depq, "none", rocks_provided, verify, depskey) + if not okfullfill then + return nil, errfullfill + end + end + util.printout() + return true + elseif err then + util.warning(err) + end + + ok, err = check_supported_platforms(rockspec) + if not ok then + return nil, err + end + + deps.report_missing_dependencies(name, version, (rockspec)[depskey].queries, deps_mode, rocks_provided) + + util.printout() + + local get_versions = prepare_get_versions(deps_mode, rocks_provided, depskey) + for _, depq in ipairs((rockspec)[depskey].queries) do + + util.printout(("%s %s depends on %s (%s)"):format( + name, version, tostring(depq), rock_status(depq, get_versions))) + + local okfulfill, found_or_err, _ = deps.fulfill_dependency(depq, deps_mode, rocks_provided, verify, depskey) + if okfulfill then + deplocks.add(depskey, depq.name, found_or_err) + else + + + + + + + + + return nil, found_or_err + end + end + + return true +end + + + + + + + + +local function deconstruct_pattern(file, pattern) + local depattern = "^" .. (pattern:gsub("%.", "%%."):gsub("%*", ".*"):gsub("?", "(.*)")) .. "$" + return (file:match(depattern)) +end + + + + + + + + +local function add_all_patterns(file, patterns, files) + for _, pattern in ipairs(patterns) do + table.insert(files, { #files + 1, (pattern:gsub("?", file)) }) + end +end + +local function get_external_deps_dirs(mode) + local patterns = cfg.external_deps_patterns + local subdirs = cfg.external_deps_subdirs + if mode == "install" then + patterns = cfg.runtime_external_deps_patterns + subdirs = cfg.runtime_external_deps_subdirs + end + local dirs = { + BINDIR = { subdir = subdirs.bin, testfile = "program", pattern = patterns.bin }, + INCDIR = { subdir = subdirs.include, testfile = "header", pattern = patterns.include }, + LIBDIR = { subdir = subdirs.lib, testfile = "library", pattern = patterns.lib }, + } + if mode == "install" then + dirs.INCDIR = nil + end + return dirs +end + +local function resolve_prefix(prefix, dirs) + if type(prefix) == "string" then + return prefix + elseif type(prefix) == "table" then + if prefix.bin then + dirs.BINDIR.subdir = prefix.bin + end + if prefix.include then + if dirs.INCDIR then + dirs.INCDIR.subdir = prefix.include + end + end + if prefix.lib then + dirs.LIBDIR.subdir = prefix.lib + end + return prefix.prefix + end +end + +local function add_patterns_for_file(files, file, patterns) + + if not (file:match("%.[a-z]+$") or file:match("%.[a-z]+%.")) then + add_all_patterns(file, patterns, files) + else + for _, pattern in ipairs(patterns) do + local matched = deconstruct_pattern(file, pattern) + if matched then + add_all_patterns(matched, patterns, files) + end + end + table.insert(files, { #files + 1, file }) + end +end + +local function check_external_dependency_at( + prefix, + name, + ext_files, + vars, + dirs, + err_files, + cache) + + local fs = require("luarocks.fs") + cache = cache or {} + + for dirname, dirdata in util.sortedpairs(dirs) do + local paths + local path_var_value = vars[name .. "_" .. dirname] + local dirdatastr = dirdata.subdir + if path_var_value then + paths = { path_var_value } + elseif type(dirdatastr) == "table" then + paths = {} + for i, v in ipairs(dirdatastr) do + paths[i] = dir.path(prefix, v) + end + else + paths = { dir.path(prefix, dirdatastr) } + end + local file_or_files = ext_files[dirdata.testfile] + if file_or_files then + local files = {} + if type(file_or_files) == "string" then + add_patterns_for_file(files, file_or_files, dirdata.pattern) + elseif type(file_or_files) == "table" then + for _, f in ipairs(file_or_files) do + add_patterns_for_file(files, f, dirdata.pattern) + end + end + + local found = false + table.sort(files, function(a, b) + if (not a[2]:match("%*")) and b[2]:match("%*") then + return true + elseif a[2]:match("%*") and (not b[2]:match("%*")) then + return false + else + return a[1] < b[1] + end + end) + for _, fa in ipairs(files) do + + local f = fa[2] + + if f:match("%.so$") or f:match("%.dylib$") or f:match("%.dll$") then + f = f:gsub("%.[^.]+$", "." .. cfg.external_lib_extension) + end + + local pattern + if f:match("%*") then + pattern = "^" .. f:gsub("([-.+])", "%%%1"):gsub("%*", ".*") .. "$" + f = "matching " .. f + end + + for _, d in ipairs(paths) do + if pattern then + if not cache[d] then + cache[d] = fs.list_dir(d) + end + local match = string.match + for _, entry in ipairs(cache[d]) do + if match(entry, pattern) then + found = true + break + end + end + else + found = fs.is_file(dir.path(d, f)) + end + if found then + dirdata.dir = d + dirdata.file = f + break + else + table.insert(err_files[dirdata.testfile], f .. " in " .. d) + end + end + if found then + break + end + end + if not found then + return nil, dirname, dirdata.testfile + end + else + + + + dirdata.dir = paths[1] + for _, p in ipairs(paths) do + if fs.exists(p) then + dirdata.dir = p + break + end + end + end + end + + for dirname, dirdata in pairs(dirs) do + vars[name .. "_" .. dirname] = dirdata.dir + vars[name .. "_" .. dirname .. "_FILE"] = dirdata.file + end + vars[name .. "_DIR"] = prefix + return true +end + +local function check_external_dependency( + name, + ext_files, + vars, + mode, + cache) + local ok + local err_dirname + local err_testfile + local err_files = { program = {}, header = {}, library = {} } + + local dirs = get_external_deps_dirs(mode) + + local prefixes + if vars[name .. "_DIR"] then + prefixes = { vars[name .. "_DIR"] } + elseif vars.DEPS_DIR then + prefixes = { vars.DEPS_DIR } + else + prefixes = cfg.external_deps_dirs + end + + for _, prefix in ipairs(prefixes) do + prefix = resolve_prefix(prefix, dirs) + if cfg.is_platform("mingw32") and name == "LUA" then + dirs.LIBDIR.pattern = fun.filter(util.deep_copy(dirs.LIBDIR.pattern), function(s) + return not s:match("%.a$") + end) + elseif cfg.is_platform("windows") and name == "LUA" then + dirs.LIBDIR.pattern = fun.filter(util.deep_copy(dirs.LIBDIR.pattern), function(s) + return not s:match("%.dll$") + end) + end + ok, err_dirname, err_testfile = check_external_dependency_at(prefix, name, ext_files, vars, dirs, err_files, cache) + if ok then + return true + end + end + + return nil, err_dirname, err_testfile, err_files +end + +function deps.autodetect_external_dependencies(build) + + if not build or not (build).modules then + return nil + end + + local extdeps = {} + local any = false + for _, data in pairs((build).modules) do + if type(data) == "table" and data.libraries then + local libraries + local librariesstr = data.libraries + if type(librariesstr) == "string" then + libraries = { librariesstr } + else + libraries = librariesstr + end + local incdirs = {} + local libdirs = {} + for _, lib in ipairs(libraries) do + local upper = lib:upper():gsub("%+", "P"):gsub("[^%w]", "_") + any = true + extdeps[upper] = { library = lib } + table.insert(incdirs, "$(" .. upper .. "_INCDIR)") + table.insert(libdirs, "$(" .. upper .. "_LIBDIR)") + end + if not data.incdirs then + data.incdirs = incdirs + end + if not data.libdirs then + data.libdirs = libdirs + end + end + end + return any and extdeps or nil +end + + + + + + + + + + + + + + +function deps.check_external_deps(rockspec, mode) + + if not rockspec.external_dependencies then + rockspec.external_dependencies = deps.autodetect_external_dependencies(rockspec.build) + end + if not rockspec.external_dependencies then + return true + end + + for name, ext_files in util.sortedpairs(rockspec.external_dependencies) do + local ok, err_dirname, err_testfile, err_files = check_external_dependency(name, ext_files, rockspec.variables, mode) + if not ok then + local lines = { "Could not find " .. err_testfile .. " file for " .. name } + + local err_paths = {} + for _, err_file in ipairs(err_files[err_testfile]) do + if not err_paths[err_file] then + err_paths[err_file] = true + table.insert(lines, " No file " .. err_file) + end + end + + table.insert(lines, "You may have to install " .. name .. " in your system and/or pass " .. name .. "_DIR or " .. name .. "_" .. err_dirname .. " to the luarocks command.") + table.insert(lines, "Example: luarocks install " .. rockspec.name .. " " .. name .. "_DIR=/usr/local") + + return nil, table.concat(lines, "\n"), "dependency" + end + end + return true +end + + + + + + + + +function deps.scan_deps(results, mdeps, name, version, deps_mode) + assert(not name:match("/")) + + local fetch = require("luarocks.fetch") + + if results[name] then + return + end + if not mdeps[name] then mdeps[name] = {} end + local mdn = mdeps[name] + local dependencies = mdn[version] + local rocks_provided + if not dependencies then + local rockspec = fetch.load_local_rockspec(path.rockspec_file(name, version), false) + if not rockspec then + return + end + dependencies = rockspec.dependencies.queries + rocks_provided = rockspec.rocks_provided + mdn[version] = dependencies + else + rocks_provided = util.get_rocks_provided() + end + + local get_versions = prepare_get_versions(deps_mode, rocks_provided, "dependencies") + + local matched = match_all_deps(dependencies, get_versions) + results[name] = version + for _, match in pairs(matched) do + deps.scan_deps(results, mdeps, match.name, match.version, deps_mode) + end +end + +local function lua_h_exists(d, luaver) + local major, minor = luaver:match("(%d+)%.(%d+)") + local luanum = ("%s%02d"):format(major, tonumber(minor)) + + local lua_h = dir.path(d, "lua.h") + local fd = io.open(lua_h) + if fd then + local data = fd:read("*a") + fd:close() + if data:match("LUA_VERSION_NUM%s*" .. tostring(luanum)) then + return d ~= nil + end + return nil, "Lua header lua.h found at " .. d .. " does not match Lua version " .. luaver .. ". You can use `luarocks config variables.LUA_INCDIR ` to set the correct location.", "dependency", 2 + end + + return nil, "Failed finding Lua header lua.h (searched at " .. d .. "). You may need to install Lua development headers. You can use `luarocks config variables.LUA_INCDIR ` to set the correct location.", "dependency", 1 +end + +local function find_lua_incdir(prefix, luaver, luajitver) + luajitver = luajitver and luajitver:gsub("%-.*", "") + local shortv = luaver:gsub("%.", "") + local incdirs = { + prefix .. "/include/lua/" .. luaver, + prefix .. "/include/lua" .. luaver, + prefix .. "/include/lua-" .. luaver, + prefix .. "/include/lua" .. shortv, + prefix .. "/include", + prefix, + luajitver and (prefix .. "/include/luajit-" .. (luajitver:match("^(%d+%.%d+)") or "")), + } + local errprio = 0 + local mainerr + for _, d in ipairs(incdirs) do + local ok, err, _, prio = lua_h_exists(d, luaver) + if ok then + return d + end + if prio > errprio then + mainerr = err + errprio = prio + end + end + + + return nil, mainerr +end + +function deps.check_lua_incdir(vars) + if vars.LUA_INCDIR_OK == "ok" then + return true + end + + local ljv = util.get_luajit_version() + + if vars.LUA_INCDIR then + local ok, err = lua_h_exists(vars.LUA_INCDIR, cfg.lua_version) + if ok then + vars.LUA_INCDIR_OK = "ok" + end + return ok, err + end + + if vars.LUA_DIR then + local d, err = find_lua_incdir(vars.LUA_DIR, cfg.lua_version, ljv) + if d then + vars.LUA_INCDIR = d + vars.LUA_INCDIR_OK = "ok" + return true + end + return nil, err + end + + return nil, "Failed finding Lua headers; neither LUA_DIR or LUA_INCDIR are set. You may need to install them or configure LUA_INCDIR.", "dependency" +end + +function deps.check_lua_libdir(vars) + if vars.LUA_LIBDIR_OK == "ok" then + return true + end + + local fs = require("luarocks.fs") + local ljv = util.get_luajit_version() + + if vars.LUA_LIBDIR and vars.LUALIB and fs.exists(dir.path(vars.LUA_LIBDIR, vars.LUALIB)) then + vars.LUA_LIBDIR_OK = "ok" + return true + end + + local shortv = cfg.lua_version:gsub("%.", "") + local libnames = { + "lua" .. cfg.lua_version, + "lua" .. shortv, + "lua-" .. cfg.lua_version, + "lua-" .. shortv, + "lua", + } + if ljv then + table.insert(libnames, 1, "luajit-" .. cfg.lua_version) + table.insert(libnames, 2, "luajit") + end + local cache = {} + local save_LUA_INCDIR = vars.LUA_INCDIR + local ok, _, _, errfiles = check_external_dependency("LUA", { library = libnames }, vars, "build", cache) + vars.LUA_INCDIR = save_LUA_INCDIR + local err + if ok then + local filename = dir.path(vars.LUA_LIBDIR, vars.LUA_LIBDIR_FILE) + local fd = io.open(filename, "r") + if fd then + if not vars.LUA_LIBDIR_FILE:match((cfg.lua_version:gsub("%.", "%%.?"))) then + + local txt = fd:read("*a") + ok = txt:find("Lua " .. cfg.lua_version, 1, true) or + txt:find("lua" .. (cfg.lua_version:gsub("%.", "")), 1, true) and + true + if not ok then + err = "Lua library at " .. filename .. " does not match Lua version " .. cfg.lua_version .. ". You can use `luarocks config variables.LUA_LIBDIR ` to set the correct location." + end + end + + fd:close() + end + end + + if ok then + vars.LUALIB = vars.LUA_LIBDIR_FILE + vars.LUA_LIBDIR_OK = "ok" + return true + else + err = err or "Failed finding the Lua library. You can use `luarocks config variables.LUA_LIBDIR ` to set the correct location." + return nil, err, "dependency", errfiles + end +end + +function deps.get_deps_mode(args) + return args.deps_mode or cfg.deps_mode +end + + + + + + + +function deps.check_dependencies(repo, deps_mode) + local rocks_dir = path.rocks_dir(repo or cfg.root_dir) + if deps_mode == "none" then deps_mode = cfg.deps_mode end + + local manifest = manif.load_manifest(rocks_dir) + if not manifest then + return + end + + for name, versions in util.sortedpairs(manifest.repository) do + for version, version_entries in util.sortedpairs(versions, vers.compare_versions) do + for _, entry in ipairs(version_entries) do + if entry.arch == "installed" then + if manifest.dependencies[name] and manifest.dependencies[name][version] then + deps.report_missing_dependencies(name, version, manifest.dependencies[name][version], deps_mode, util.get_rocks_provided()) + end + end + end + end + end +end + +return deps diff --git a/src/luarocks/dir.lua b/src/luarocks/dir.lua new file mode 100644 index 00000000..b9989bbe --- /dev/null +++ b/src/luarocks/dir.lua @@ -0,0 +1,65 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local package = _tl_compat and _tl_compat.package or package; local string = _tl_compat and _tl_compat.string or string + +local dir = {} + + + + + +local core = require("luarocks.core.dir") + +dir.path = core.path +dir.split_url = core.split_url +dir.normalize = core.normalize + +local dir_sep = package.config:sub(1, 1) + + + + + +function dir.base_name(pathname) + + local b + b = pathname:gsub("[/\\]", "/") + b = b:gsub("/*$", "") + b = b:match(".*[/\\]([^/\\]*)") + b = b or pathname + + return b +end + + + + + + +function dir.dir_name(pathname) + + local d + d = pathname:gsub("[/\\]", "/") + d = d:gsub("/*$", "") + d = d:match("(.*)[/]+[^/]*") + d = d or "" + d = d:gsub("/", dir_sep) + + return d +end + + + +function dir.is_basic_protocol(protocol) + return protocol == "http" or protocol == "https" or protocol == "ftp" or protocol == "file" +end + +function dir.deduce_base_dir(url) + + local known_exts = {} + for _, ext in ipairs({ "zip", "git", "tgz", "tar", "gz", "bz2" }) do + known_exts[ext] = "" + end + local base = dir.base_name(url) + return (base:gsub("%.([^.]*)$", known_exts):gsub("%.tar", "")) +end + +return dir diff --git a/src/luarocks/download.lua b/src/luarocks/download.lua new file mode 100644 index 00000000..068ade96 --- /dev/null +++ b/src/luarocks/download.lua @@ -0,0 +1,76 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local pairs = _tl_compat and _tl_compat.pairs or pairs; local download = {} + + +local path = require("luarocks.path") +local fetch = require("luarocks.fetch") +local search = require("luarocks.search") +local queries = require("luarocks.queries") +local fs = require("luarocks.fs") +local dir = require("luarocks.dir") +local util = require("luarocks.util") + +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(), "read") + if ok then + return pathname + else + return nil, err + end + else + + local ok, err = fetch.fetch_url(filename) + return ok, err + end +end + +function download.download_all(arch, name, namespace, version) + local substring = (name == "") + local query = queries.new(name, namespace, version, substring, arch) + local search_err + + 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 + + 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 + + local rock = util.format_rock_name(name, namespace, version) + return nil, "Could not find a result named " .. rock .. (search_err and ": " .. search_err or ".") +end + +function download.download_file(arch, name, namespace, version, check_lua_versions) + local query = queries.new(name, namespace, version, false, arch) + local search_err + + local url + url, search_err = search.find_rock_checking_lua_versions(query, check_lua_versions) + if url then + return get_file(url) + end + + local rock = util.format_rock_name(name, namespace, version) + return nil, "Could not find a result named " .. rock .. (search_err and ": " .. search_err or ".") +end + +return download diff --git a/src/luarocks/fetch.lua b/src/luarocks/fetch.lua new file mode 100644 index 00000000..7472ee3f --- /dev/null +++ b/src/luarocks/fetch.lua @@ -0,0 +1,615 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local assert = _tl_compat and _tl_compat.assert or assert; local io = _tl_compat and _tl_compat.io or io; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local pcall = _tl_compat and _tl_compat.pcall or pcall; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table + +local fetch = {Fetch = {}, } + + + + + + + + + + + + + + +local fs = require("luarocks.fs") +local dir = require("luarocks.dir") +local rockspecs = require("luarocks.rockspecs") +local signing = require("luarocks.signing") +local persist = require("luarocks.persist") +local util = require("luarocks.util") +local cfg = require("luarocks.core.cfg") + + + + + + + + + + + + + + + + + + + + + + + +function fetch.fetch_caching(url, mirroring) + local repo_url, filename = url:match("^(.*)/([^/]+)$") + local name = repo_url:gsub("[/:]", "_") + local cache_dir = dir.path(cfg.local_cache, name) + local ok = fs.make_dir(cache_dir) + + local cachefile = dir.path(cache_dir, filename) + local checkfile = cachefile .. ".check" + + if (fs.file_age(checkfile) < 10 or + cfg.aggressive_cache and (not name:match("^manifest"))) and fs.exists(cachefile) then + + return cachefile, nil, nil, true + end + + local lock, errlock + if ok then + lock, errlock = fs.lock_access(cache_dir) + end + + if not (ok and lock) then + cfg.local_cache = fs.make_temp_dir("local_cache") + if not cfg.local_cache then + return nil, "Failed creating temporary local_cache directory" + end + cache_dir = dir.path(cfg.local_cache, name) + ok = fs.make_dir(cache_dir) + if not ok then + return nil, "Failed creating temporary cache directory " .. cache_dir + end + lock = fs.lock_access(cache_dir) + end + + local file, err, errcode, from_cache = fetch.fetch_url(url, cachefile, true, mirroring) + if not file then + fs.unlock_access(lock) + return nil, err or "Failed downloading " .. url, errcode + end + + local fd, erropen = io.open(checkfile, "wb") + if erropen then + fs.unlock_access(lock) + return nil, erropen + end + fd:write("!") + fd:close() + + fs.unlock_access(lock) + return file, nil, nil, from_cache +end + +local function ensure_trailing_slash(url) + return (url:gsub("/*$", "/")) +end + +local function is_url_relative_to_rocks_servers(url, servers) + for _, item in ipairs(servers) do + if type(item) == "table" then + for i, s in ipairs(item) do + local base = ensure_trailing_slash(s) + if string.find(url, base, 1, true) == 1 then + return i, url:sub(#base + 1), item + end + end + end + end +end + +local function download_with_mirrors(url, filename, cache, servers) + local idx, rest, mirrors = is_url_relative_to_rocks_servers(url, servers) + + if not idx then + + return fs.download(url, filename, cache) + end + + + local err = "\n" + for i = idx, #mirrors do + local try_url = ensure_trailing_slash(mirrors[i]) .. rest + if i > idx then + util.warning("Failed downloading. Attempting mirror at " .. try_url) + end + local name, _, _, from_cache = fs.download(try_url, filename, cache) + if name then + return name, nil, nil, from_cache + else + err = err .. name .. "\n" + end + end + + return nil, err, "network" +end + + + + + + + + + + + + + + + + + + + + + + + + +function fetch.fetch_url(url, filename, cache, mirroring) + + local protocol, pathname = dir.split_url(url) + if protocol == "file" then + local fullname = fs.absolute_name(pathname) + if not fs.exists(fullname) then + local hint = (not pathname:match("^/")) and + (" - note that given path in rockspec is not absolute: " .. url) or + "" + return nil, "Local file not found: " .. fullname .. hint + end + filename = filename or dir.base_name(pathname) + local dstname = fs.absolute_name(dir.path(".", filename)) + local ok, err + if fullname == dstname then + ok = true + else + ok, err = fs.copy(fullname, dstname) + end + if ok then + return dstname + else + return nil, "Failed copying local file " .. fullname .. " to " .. dstname .. ": " .. err + end + elseif dir.is_basic_protocol(protocol) then + local name, err, err_code, from_cache + if mirroring ~= "no_mirror" then + name, err, err_code, from_cache = download_with_mirrors(url, filename, cache, cfg.rocks_servers) + else + name, err, err_code, from_cache = fs.download(url, filename, cache) + end + if not name then + return nil, "Failed downloading " .. url .. (err and " - " .. err or ""), err_code + end + return name, nil, nil, from_cache + else + return nil, "Unsupported protocol " .. protocol + end +end + + + + + + + + + + + + +function fetch.fetch_url_at_temp_dir(url, tmpname, filename, cache) + filename = filename or dir.base_name(url) + + local protocol, pathname = dir.split_url(url) + if protocol == "file" then + if fs.exists(pathname) then + return pathname, dir.dir_name(fs.absolute_name(pathname)) + else + return nil, "File not found: " .. pathname + end + else + local temp_dir, errmake = fs.make_temp_dir(tmpname) + if not temp_dir then + return nil, "Failed creating temporary directory " .. tmpname .. ": " .. errmake + end + util.schedule_function(fs.delete, temp_dir) + local ok, errchange = fs.change_dir(temp_dir) + if not ok then return nil, errchange end + + local file, err, errcode + + if cache then + local cachefile + cachefile, err, errcode = fetch.fetch_caching(url) + + if cachefile then + file = dir.path(temp_dir, filename) + fs.copy(cachefile, file) + end + end + + if not file then + file, err, errcode = fetch.fetch_url(url, filename, cache) + end + + fs.pop_dir() + if not file then + return nil, "Error fetching file: " .. err, errcode + end + + return file, temp_dir + end +end + + + + + + + + + + + + + +function fetch.find_base_dir(file, temp_dir, src_url, src_dir) + local ok, err = fs.change_dir(temp_dir) + if not ok then return nil, err end + fs.unpack_archive(file) + + if not src_dir then + local rockspec = { + source = { + file = file, + dir = src_dir, + url = src_url, + }, + } + ok, err = fetch.find_rockspec_source_dir(rockspec, ".") + if ok then + src_dir = rockspec.source.dir + end + end + + local inferred_dir = src_dir or dir.deduce_base_dir(src_url) + local found_dir = nil + if fs.exists(inferred_dir) then + found_dir = inferred_dir + else + util.printerr("Directory " .. inferred_dir .. " not found") + local files = fs.list_dir() + if files then + table.sort(files) + for _, filename in ipairs(files) do + if fs.is_dir(filename) then + util.printerr("Found " .. filename) + found_dir = filename + break + end + end + end + end + fs.pop_dir() + return inferred_dir, found_dir +end + + +local function fetch_and_verify_signature_for(url, filename, tmpdir) + local sig_url = signing.signature_url(url) + local sig_file, errfetch, errcode = fetch.fetch_url_at_temp_dir(sig_url, tmpdir) + if not sig_file then + return nil, "Could not fetch signature file for verification: " .. errfetch, errcode + end + + local ok, err = signing.verify_signature(filename, sig_file) + if not ok then + return nil, "Failed signature verification: " .. err + end + + return fs.absolute_name(sig_file) +end + + + + + + + + + + +function fetch.fetch_and_unpack_rock(url, dest, verify) + + local name = dir.base_name(url):match("(.*)%.[^.]*%.rock") + local tmpname = "luarocks-rock-" .. name + + local rock_file, err, errcode = fetch.fetch_url_at_temp_dir(url, tmpname, nil, true) + if not rock_file then + return nil, "Could not fetch rock file: " .. err, errcode + end + + local sig_file + if verify then + sig_file, err = fetch_and_verify_signature_for(url, rock_file, tmpname) + if err then + return nil, err + end + end + + rock_file = fs.absolute_name(rock_file) + + local unpack_dir + if dest then + unpack_dir = dest + local ok, errmake = fs.make_dir(unpack_dir) + if not ok then + return nil, "Failed unpacking rock file: " .. errmake + end + else + unpack_dir, err = fs.make_temp_dir(name) + if not unpack_dir then + return nil, "Failed creating temporary dir: " .. err + end + end + if not dest then + util.schedule_function(fs.delete, unpack_dir) + end + local ok, errchange = fs.change_dir(unpack_dir) + if not ok then return nil, errchange end + ok, err = fs.unzip(rock_file) + if not ok then + return nil, "Failed unpacking rock file: " .. rock_file .. ": " .. err + end + if sig_file then + ok, err = fs.copy(sig_file, ".") + if not ok then + return nil, "Failed copying signature file" + end + end + fs.pop_dir() + return unpack_dir +end + + + + + + + + +function fetch.load_local_rockspec(rel_filename, quick) + local abs_filename = fs.absolute_name(rel_filename) + + local basename = dir.base_name(abs_filename) + if basename ~= "rockspec" then + if not basename:match("(.*)%-[^-]*%-[0-9]*") then + return nil, "Expected filename in format 'name-version-revision.rockspec'." + end + end + + local tbl, err = persist.load_into_table(abs_filename) + if not tbl and type(err) == "string" then + return nil, "Could not load rockspec file " .. abs_filename .. " (" .. err .. ")" + end + + local rockspec, errrock = rockspecs.from_persisted_table(abs_filename, tbl, err, quick) + if not rockspec then + return nil, abs_filename .. ": " .. errrock + end + + local name_version = rockspec.package:lower() .. "-" .. rockspec.version + if basename ~= "rockspec" and basename ~= name_version .. ".rockspec" then + return nil, "Inconsistency between rockspec filename (" .. basename .. ") and its contents (" .. name_version .. ".rockspec)." + end + + return rockspec +end + + + + + + + + + + + +function fetch.load_rockspec(url, location, verify) + + local name + local basename = dir.base_name(url) + if basename == "rockspec" then + name = "rockspec" + else + name = basename:match("(.*)%.rockspec") + if not name then + return nil, "Filename '" .. url .. "' does not look like a rockspec." + end + end + + local tmpname = "luarocks-rockspec-" .. name + local filename, err, errcode, ok + if location then + ok, err = fs.change_dir(location) + if not ok then return nil, err end + filename, err = fetch.fetch_url(url) + fs.pop_dir() + else + filename, err, errcode = fetch.fetch_url_at_temp_dir(url, tmpname, nil, true) + end + if not filename then + return nil, err, errcode + end + + if verify then + local _, errfetch = fetch_and_verify_signature_for(url, filename, tmpname) + if err then + return nil, errfetch + end + end + + return fetch.load_local_rockspec(filename) +end + + + + + + + + + + +function fetch.get_sources(rockspec, extract, dest_dir) + + local url = rockspec.source.url + local name = rockspec.name .. "-" .. rockspec.version + local filename = rockspec.source.file + local source_file, store_dir + local ok, err, errcode + if dest_dir then + ok, err = fs.change_dir(dest_dir) + if not ok then return nil, err, "dest_dir" end + source_file, err, errcode = fetch.fetch_url(url, filename) + fs.pop_dir() + store_dir = dest_dir + else + source_file, store_dir, errcode = fetch.fetch_url_at_temp_dir(url, "luarocks-source-" .. name, filename) + end + if not source_file then + return nil, err or store_dir, errcode + end + if rockspec.source.md5 then + if not fs.check_md5(source_file, rockspec.source.md5) then + return nil, "MD5 check for " .. filename .. " has failed.", "md5" + end + end + if extract then + ok, err = fs.change_dir(store_dir) + if not ok then return nil, err end + ok, err = fs.unpack_archive(rockspec.source.file) + if not ok then return nil, err end + ok, err = fetch.find_rockspec_source_dir(rockspec, ".") + if not ok then return nil, err end + fs.pop_dir() + end + return source_file, store_dir +end + +function fetch.find_rockspec_source_dir(rockspec, store_dir) + local ok, err = fs.change_dir(store_dir) + if not ok then return nil, err end + + local file_count, dir_count = 0, 0 + local found_dir + + if rockspec.source.dir and fs.exists(rockspec.source.dir) then + ok, err = true, nil + elseif rockspec.source.file and rockspec.source.dir then + ok, err = nil, "Directory " .. rockspec.source.dir .. " not found inside archive " .. rockspec.source.file + elseif not rockspec.source.dir_set then + + local name = dir.base_name(rockspec.source.file or rockspec.source.url or "") + + if name:match("%.lua$") or name:match("%.c$") then + if fs.is_file(name) then + rockspec.source.dir = "." + ok, err = true, nil + end + end + + if not rockspec.source.dir then + for file in fs.dir() do + file_count = file_count + 1 + if fs.is_dir(file) then + dir_count = dir_count + 1 + found_dir = file + end + end + + if dir_count == 1 then + rockspec.source.dir = found_dir + ok, err = true, nil + else + ok, err = nil, "Could not determine source directory from rock contents (" .. tostring(file_count) .. " file(s), " .. tostring(dir_count) .. " dir(s))" + end + end + else + ok, err = nil, "Could not determine source directory, please set source.dir in rockspec." + end + + fs.pop_dir() + + assert(rockspec.source.dir or not ok) + return ok, err +end + + + + + + + + + + +function fetch.fetch_sources(rockspec, extract, dest_dir) + + + + if rockspec.source.url:match("^git://github%.com/") or + rockspec.source.url:match("^git://www%.github%.com/") then + rockspec.source.url = rockspec.source.url:gsub("^git://", "git+https://") + rockspec.source.protocol = "git+https" + end + + local protocol = rockspec.source.protocol + local ok, err, proto + + if dir.is_basic_protocol(protocol) then + proto = fetch + else + ok, proto = pcall(require, "luarocks.fetch." .. protocol:gsub("[+-]", "_")) + if not ok then + return nil, "Unknown protocol " .. protocol + end + end + + if cfg.only_sources_from and + rockspec.source.pathname and + #rockspec.source.pathname > 0 then + if #cfg.only_sources_from == 0 then + return nil, "Can't download " .. rockspec.source.url .. " -- download from remote servers disabled" + elseif rockspec.source.pathname:find(cfg.only_sources_from, 1, true) ~= 1 then + return nil, "Can't download " .. rockspec.source.url .. " -- only downloading from " .. cfg.only_sources_from + end + end + + local source_file, store_dir = proto.get_sources(rockspec, extract, dest_dir) + if not source_file then return nil, store_dir end + + ok, err = fetch.find_rockspec_source_dir(rockspec, store_dir) + if not ok then return nil, err, "source.dir", source_file, store_dir end + + return source_file, store_dir +end + +return fetch diff --git a/src/luarocks/fetch/cvs.lua b/src/luarocks/fetch/cvs.lua new file mode 100644 index 00000000..6aabfb31 --- /dev/null +++ b/src/luarocks/fetch/cvs.lua @@ -0,0 +1,53 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local table = _tl_compat and _tl_compat.table or table; local _tl_table_unpack = unpack or table.unpack + +local cvs = {} + + +local fs = require("luarocks.fs") +local dir = require("luarocks.dir") +local util = require("luarocks.util") + + + + + + + + + + +function cvs.get_sources(rockspec, extract, dest_dir) + local cvs_cmd = rockspec.variables.CVS + local ok, err_msg = fs.is_tool_available(cvs_cmd, "CVS") + if not ok then + return nil, err_msg + end + + local name_version = rockspec.name .. "-" .. rockspec.version + local module = rockspec.source.module or dir.base_name(rockspec.source.url) + local command = { cvs_cmd, "-d" .. rockspec.source.pathname, "export", module } + if rockspec.source.tag then + table.insert(command, 4, "-r") + table.insert(command, 5, rockspec.source.tag) + end + local store_dir + if not dest_dir then + store_dir = fs.make_temp_dir(name_version) + if not store_dir then + return nil, "Failed creating temporary directory." + end + util.schedule_function(fs.delete, store_dir) + else + store_dir = dest_dir + end + local okchange, err = fs.change_dir(store_dir) + if not okchange then return nil, err end + if not fs.execute(_tl_table_unpack(command)) then + return nil, "Failed fetching files from CVS." + end + fs.pop_dir() + return module, store_dir +end + + +return cvs diff --git a/src/luarocks/fetch/git.lua b/src/luarocks/fetch/git.lua new file mode 100644 index 00000000..b32dfb1b --- /dev/null +++ b/src/luarocks/fetch/git.lua @@ -0,0 +1,166 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local io = _tl_compat and _tl_compat.io or io; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table; local _tl_table_unpack = unpack or table.unpack + +local git = {} + + + +local fs = require("luarocks.fs") +local dir = require("luarocks.dir") +local vers = require("luarocks.core.vers") +local util = require("luarocks.util") + + + + +local cached_git_version + + + + +local function git_version(git_cmd) + if not cached_git_version then + local version_line = io.popen(fs.Q(git_cmd) .. ' --version'):read() + local version_string = version_line:match('%d-%.%d+%.?%d*') + cached_git_version = vers.parse_version(version_string) + end + + return cached_git_version +end + + + + + +local function git_is_at_least(git_cmd, version) + return git_version(git_cmd) >= vers.parse_version(version) +end + + + + + + + +local function git_can_clone_by_tag(git_cmd) + return git_is_at_least(git_cmd, "1.7.10") +end + + + + + +local function git_supports_shallow_submodules(git_cmd) + return git_is_at_least(git_cmd, "1.8.4") +end + + + + +local function git_supports_shallow_recommendations(git_cmd) + return git_is_at_least(git_cmd, "2.10.0") +end + +local function git_identifier(git_cmd, ver) + if not (ver:match("^dev%-%d+$") or ver:match("^scm%-%d+$")) then + return nil + end + local pd = io.popen(fs.command_at(fs.current_dir(), fs.Q(git_cmd) .. " log --pretty=format:%ai_%h -n 1")) + if not pd then + return nil + end + local date_hash = pd:read("*l") + pd:close() + if not date_hash then + return nil + end + local date, time, _tz, hash = date_hash:match("([^%s]+) ([^%s]+) ([^%s]+)_([^%s]+)") + date = date:gsub("%-", "") + time = time:gsub(":", "") + return date .. "." .. time .. "." .. hash +end + + + + + + + + +function git.get_sources(rockspec, extract, dest_dir, depth) + + local git_cmd = rockspec.variables.GIT + local name_version = rockspec.name .. "-" .. rockspec.version + local module = dir.base_name(rockspec.source.url) + + module = module:gsub("%.git$", "") + + local ok_available, err_msg = fs.is_tool_available(git_cmd, "Git") + if not ok_available then + return nil, err_msg + end + + local store_dir + if not dest_dir then + store_dir = fs.make_temp_dir(name_version) + if not store_dir then + return nil, "Failed creating temporary directory." + end + util.schedule_function(fs.delete, store_dir) + else + store_dir = dest_dir + end + store_dir = fs.absolute_name(store_dir) + local ok, err = fs.change_dir(store_dir) + if not ok then return nil, err end + + local command = { fs.Q(git_cmd), "clone", depth or "--depth=1", rockspec.source.url, module } + local tag_or_branch = rockspec.source.tag or rockspec.source.branch + + + if tag_or_branch == "master" then tag_or_branch = nil end + if tag_or_branch then + if git_can_clone_by_tag(git_cmd) then + + + table.insert(command, 3, "--branch=" .. tag_or_branch) + end + end + if not fs.execute(_tl_table_unpack(command)) then + return nil, "Failed cloning git repository." + end + ok, err = fs.change_dir(module) + if not ok then return nil, err end + if tag_or_branch and not git_can_clone_by_tag() then + if not fs.execute(fs.Q(git_cmd), "checkout", tag_or_branch) then + return nil, 'Failed to check out the "' .. tag_or_branch .. '" tag or branch.' + end + end + + + if rockspec:format_is_at_least("3.0") then + command = { fs.Q(git_cmd), "submodule", "update", "--init", "--recursive" } + + if git_supports_shallow_recommendations(git_cmd) then + table.insert(command, 5, "--recommend-shallow") + elseif git_supports_shallow_submodules(git_cmd) then + + table.insert(command, 5, "--depth=1") + end + + if not fs.execute(_tl_table_unpack(command)) then + return nil, 'Failed to fetch submodules.' + end + end + + if not rockspec.source.tag then + rockspec.source.identifier = git_identifier(git_cmd, rockspec.version) + end + + fs.delete(dir.path(store_dir, module, ".git")) + fs.delete(dir.path(store_dir, module, ".gitignore")) + fs.pop_dir() + fs.pop_dir() + return module, store_dir +end + +return git diff --git a/src/luarocks/fetch/git_file.lua b/src/luarocks/fetch/git_file.lua new file mode 100644 index 00000000..60d06323 --- /dev/null +++ b/src/luarocks/fetch/git_file.lua @@ -0,0 +1,22 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local string = _tl_compat and _tl_compat.string or string + +local git_file = {} + + +local git = require("luarocks.fetch.git") + + + + + + + + + + +function git_file.get_sources(rockspec, extract, dest_dir) + rockspec.source.url = rockspec.source.url:gsub("^git.file://", "") + return git.get_sources(rockspec, extract, dest_dir) +end + +return git_file diff --git a/src/luarocks/fetch/git_http.lua b/src/luarocks/fetch/git_http.lua new file mode 100644 index 00000000..9275209c --- /dev/null +++ b/src/luarocks/fetch/git_http.lua @@ -0,0 +1,29 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local string = _tl_compat and _tl_compat.string or string + + + + + + + + +local git_http = {} + + +local git = require("luarocks.fetch.git") + + + + + + + + + + +function git_http.get_sources(rockspec, extract, dest_dir) + rockspec.source.url = rockspec.source.url:gsub("^git.", "") + return git.get_sources(rockspec, extract, dest_dir, "--") +end + +return git_http diff --git a/src/luarocks/fetch/git_https.lua b/src/luarocks/fetch/git_https.lua new file mode 100644 index 00000000..463f2f08 --- /dev/null +++ b/src/luarocks/fetch/git_https.lua @@ -0,0 +1,7 @@ + + + + + + +return require("luarocks.fetch.git_http") diff --git a/src/luarocks/fetch/git_ssh.lua b/src/luarocks/fetch/git_ssh.lua new file mode 100644 index 00000000..8a95a722 --- /dev/null +++ b/src/luarocks/fetch/git_ssh.lua @@ -0,0 +1,35 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local string = _tl_compat and _tl_compat.string or string + + + + + + + + +local git_ssh = {} + + +local git = require("luarocks.fetch.git") + + + + + + + + + + +function git_ssh.get_sources(rockspec, extract, dest_dir) + rockspec.source.url = rockspec.source.url:gsub("^git.", "") + + + if rockspec.source.url:match("^ssh://[^/]+:[^%d]") then + rockspec.source.url = rockspec.source.url:gsub("^ssh://", "") + end + + return git.get_sources(rockspec, extract, dest_dir, "--") +end + +return git_ssh diff --git a/src/luarocks/fetch/hg.lua b/src/luarocks/fetch/hg.lua new file mode 100644 index 00000000..5265920f --- /dev/null +++ b/src/luarocks/fetch/hg.lua @@ -0,0 +1,64 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table; local _tl_table_unpack = unpack or table.unpack + +local hg = {} + + +local fs = require("luarocks.fs") +local dir = require("luarocks.dir") +local util = require("luarocks.util") + + + + + + + + + + +function hg.get_sources(rockspec, extract, dest_dir) + + local hg_cmd = rockspec.variables.HG + local ok_available, err_msg = fs.is_tool_available(hg_cmd, "Mercurial") + if not ok_available then + return nil, err_msg + end + + local name_version = rockspec.name .. "-" .. rockspec.version + + local url = rockspec.source.url:gsub("^hg://", "") + + local module = dir.base_name(url) + + local command = { hg_cmd, "clone", url, module } + local tag_or_branch = rockspec.source.tag or rockspec.source.branch + if tag_or_branch then + command = { hg_cmd, "clone", "--rev", tag_or_branch, url, module } + end + local store_dir + if not dest_dir then + store_dir = fs.make_temp_dir(name_version) + if not store_dir then + return nil, "Failed creating temporary directory." + end + util.schedule_function(fs.delete, store_dir) + else + store_dir = dest_dir + end + local ok, err = fs.change_dir(store_dir) + if not ok then return nil, err end + if not fs.execute(_tl_table_unpack(command)) then + return nil, "Failed cloning hg repository." + end + ok, err = fs.change_dir(module) + if not ok then return nil, err end + + fs.delete(dir.path(store_dir, module, ".hg")) + fs.delete(dir.path(store_dir, module, ".hgignore")) + fs.pop_dir() + fs.pop_dir() + return module, store_dir +end + + +return hg diff --git a/src/luarocks/fetch/hg_http.lua b/src/luarocks/fetch/hg_http.lua new file mode 100644 index 00000000..9f56c02d --- /dev/null +++ b/src/luarocks/fetch/hg_http.lua @@ -0,0 +1,27 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local string = _tl_compat and _tl_compat.string or string + + + + + + +local hg_http = {} + + +local hg = require("luarocks.fetch.hg") + + + + + + + + + + +function hg_http.get_sources(rockspec, extract, dest_dir) + rockspec.source.url = rockspec.source.url:gsub("^hg.", "") + return hg.get_sources(rockspec, extract, dest_dir) +end + +return hg_http diff --git a/src/luarocks/fetch/hg_https.lua b/src/luarocks/fetch/hg_https.lua new file mode 100644 index 00000000..1b6475ad --- /dev/null +++ b/src/luarocks/fetch/hg_https.lua @@ -0,0 +1,8 @@ + + + + + + + +return require("luarocks.fetch.hg_http") diff --git a/src/luarocks/fetch/hg_ssh.lua b/src/luarocks/fetch/hg_ssh.lua new file mode 100644 index 00000000..1b6475ad --- /dev/null +++ b/src/luarocks/fetch/hg_ssh.lua @@ -0,0 +1,8 @@ + + + + + + + +return require("luarocks.fetch.hg_http") diff --git a/src/luarocks/fetch/sscm.lua b/src/luarocks/fetch/sscm.lua new file mode 100644 index 00000000..f635c02f --- /dev/null +++ b/src/luarocks/fetch/sscm.lua @@ -0,0 +1,45 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local io = _tl_compat and _tl_compat.io or io; local string = _tl_compat and _tl_compat.string or string + +local sscm = {} + + +local fs = require("luarocks.fs") +local dir = require("luarocks.dir") + + + + + + + + + + +function sscm.get_sources(rockspec, extract, dest_dir) + + local sscm_cmd = rockspec.variables.SSCM + local module = rockspec.source.module or dir.base_name(rockspec.source.url) + local branch, repository = string.match(rockspec.source.pathname, "^([^/]*)/(.*)") + if not branch or not repository then + return nil, "Error retrieving branch and repository from rockspec." + end + + local working_dir + local tmp = io.popen(string.format(sscm_cmd .. [[ property "/" -d -b%s -p%s]], branch, repository)) + for line in tmp:lines() do + + working_dir = string.match(line, "Working directory:[%s]*(.*)%c$") + if working_dir then break end + end + tmp:close() + if not working_dir then + return nil, "Error retrieving working directory from SSCM." + end + if not fs.execute(sscm_cmd, "get", "*", "-e", "-r", "-b" .. branch, "-p" .. repository, "-tmodify", "-wreplace") then + return nil, "Failed fetching files from SSCM." + end + + return module, working_dir +end + +return sscm diff --git a/src/luarocks/fetch/svn.lua b/src/luarocks/fetch/svn.lua new file mode 100644 index 00000000..cd467cef --- /dev/null +++ b/src/luarocks/fetch/svn.lua @@ -0,0 +1,63 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table; local _tl_table_unpack = unpack or table.unpack + +local svn = {} + + +local fs = require("luarocks.fs") +local dir = require("luarocks.dir") +local util = require("luarocks.util") + + + + + + + + + + +function svn.get_sources(rockspec, extract, dest_dir) + + local svn_cmd = rockspec.variables.SVN + local ok, err_msg = fs.is_tool_available(svn_cmd, "Subversion") + if not ok then + return nil, err_msg + end + + local name_version = rockspec.name .. "-" .. rockspec.version + local module = rockspec.source.module or dir.base_name(rockspec.source.url) + local url = rockspec.source.url:gsub("^svn://", "") + local command = { svn_cmd, "checkout", url, module } + if rockspec.source.tag then + table.insert(command, 5, "-r") + table.insert(command, 6, rockspec.source.tag) + end + local store_dir + if not dest_dir then + store_dir = fs.make_temp_dir(name_version) + if not store_dir then + return nil, "Failed creating temporary directory." + end + util.schedule_function(fs.delete, store_dir) + else + store_dir = dest_dir + end + local okchange, err = fs.change_dir(store_dir) + if not okchange then return nil, err end + if not fs.execute(_tl_table_unpack(command)) then + return nil, "Failed fetching files from Subversion." + end + okchange, err = fs.change_dir(module) + if not okchange then return nil, err end + for _, d in ipairs(fs.find(".")) do + if dir.base_name(d) == ".svn" then + fs.delete(dir.path(store_dir, module, d)) + end + end + fs.pop_dir() + fs.pop_dir() + return module, store_dir +end + + +return svn diff --git a/src/luarocks/fun.lua b/src/luarocks/fun.lua new file mode 100644 index 00000000..86030ccc --- /dev/null +++ b/src/luarocks/fun.lua @@ -0,0 +1,138 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local math = _tl_compat and _tl_compat.math or math; local table = _tl_compat and _tl_compat.table or table; local _tl_table_unpack = unpack or table.unpack + +local fun = {} + + +function fun.concat(xs, ys) + local rs = {} + local n = #xs + for i = 1, n do + rs[i] = xs[i] + end + for i = 1, #ys do + rs[i + n] = ys[i] + end + + return rs +end + +function fun.contains(xs, v) + for _, x in ipairs(xs) do + if v == x then + return true + end + end + return false +end + +function fun.map(xs, f) + local rs = {} + for i = 1, #xs do + rs[i] = f(xs[i]) + end + return rs +end + +function fun.filter(xs, f) + local rs = {} + for i = 1, #xs do + local v = xs[i] + if f(v) then + rs[#rs + 1] = v + end + end + return rs +end + +function fun.reverse_in(t) + for i = 1, math.floor(#t / 2) do + local m, n = i, #t - i + 1 + local a, b = t[m], t[n] + t[m] = b + t[n] = a + end + return t +end + +function fun.sort_in(t, f) + table.sort(t, f) + return t +end + +function fun.flip(f) + return function(a, b) + return f(b, a) + end +end + +function fun.find(xs, f) + if type(xs) == "table" then + for _, x in ipairs(xs) do + local r = f(x) + if r then + return r + end + end + else + for x in xs do + local r = f(x) + if r then + return r + end + end + end +end + + +function fun.partial(f, ...) + local n = select("#", ...) + if n == 1 then + local a = ... + return function(...) + return f(a, ...) + end + elseif n == 2 then + local a, b = ... + return function(...) + return f(a, b, ...) + end + else + local pargs = { n = n, ... } + return function(...) + local m = select("#", ...) + local fargs = { ... } + local args = {} + for i = 1, n do + args[i] = pargs[i] + end + for i = 1, m do + args[i + n] = fargs[i] + end + return f(_tl_table_unpack(args, 1, n + m)) + end + end +end + +function fun.memoize(fn) + local memory = setmetatable({}, { __mode = "k" }) + local errors = setmetatable({}, { __mode = "k" }) + local NIL = {} + return function(a) + if memory[a] then + if memory[a] == NIL then + return nil, errors[a] + end + return memory[a] + end + local ret1, ret2 = fn(a) + if ret1 then + memory[a] = ret1 + else + memory[a] = NIL + errors[a] = ret2 + end + return ret1, ret2 + end +end + +return fun diff --git a/src/luarocks/loader.lua b/src/luarocks/loader.lua new file mode 100644 index 00000000..ac7bb5fb --- /dev/null +++ b/src/luarocks/loader.lua @@ -0,0 +1,333 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local debug = _tl_compat and _tl_compat.debug or debug; local io = _tl_compat and _tl_compat.io or io; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local package = _tl_compat and _tl_compat.package or package; local pairs = _tl_compat and _tl_compat.pairs or pairs; local pcall = _tl_compat and _tl_compat.pcall or pcall; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table + + + + + + +local loaders = package.loaders or package.searchers +local require, ipairs, table, type, next, tostring, error = +require, ipairs, table, type, next, tostring, error + +local loader = {} + + + + +local is_clean = not package.loaded["luarocks.core.cfg"] + + +local cfg = require("luarocks.core.cfg") +local cfg_ok, _err = cfg.init() +if cfg_ok then + cfg.init_package_paths() +end + +local path = require("luarocks.core.path") +local manif = require("luarocks.core.manif") +local vers = require("luarocks.core.vers") + + + + + + + + + + + + + + + + + + + + + + + + + +local temporary_global = false +local status, luarocks_value = pcall(function() + return luarocks +end) +if status and luarocks_value then + + + + luarocks.loader = loader +else + + + + + + local info = debug and debug.getinfo(2, "nS") + if info and info.what == "C" and not info.name then + luarocks = { loader = loader } + temporary_global = true + + + end +end + + + + + +loader.context = {} + + + + + + +function loader.add_context(name, version) + if temporary_global then + + + luarocks = nil + temporary_global = false + end + + local tree_manifests = manif.load_rocks_tree_manifests() + if not tree_manifests then + return + end + + manif.scan_dependencies(name, version, tree_manifests, loader.context) +end + + + + + + + +local function sort_versions(a, b) + return a.version > b.version +end + + + + + + + + + + + + + + + + +local function call_other_loaders(module, name, version, module_name) + for _, a_loader in ipairs(loaders) do + if a_loader ~= loader.luarocks_loader then + local results = { a_loader(module_name) } + local f = results[1] + if type(f) == "function" then + if #results == 2 then + return f, results[2] + else + return f + end + end + end + end + return "Failed loading module " .. module .. " in LuaRocks rock " .. name .. " " .. version +end + + + + + + + + + + + + + + + +local function add_providers(providers, entries, tree, module, filter_name) + for i, entry in ipairs(entries) do + local name, version = entry:match("^([^/]*)/(.*)$") + + local file_name = tree.manifest.repository[name][version][1].modules[module] + if type(file_name) ~= "string" then + error("Invalid data in manifest file for module " .. tostring(module) .. " (invalid data for " .. tostring(name) .. " " .. tostring(version) .. ")") + end + + file_name = filter_name(file_name, name, version, tree.tree, i) + + if loader.context[name] == version then + return name, version, file_name + end + + table.insert(providers, { + name = name, + version = vers.parse_version(version), + module_name = file_name, + tree = tree, + }) + end +end + + + + + + + + + + + + + +local function select_module(module, filter_name) + + local tree_manifests = manif.load_rocks_tree_manifests() + if not tree_manifests then + return nil + end + + local providers = {} + local initmodule + for _, tree in ipairs(tree_manifests) do + local entries = tree.manifest.modules[module] + if entries then + local n, v, f = add_providers(providers, entries, tree, module, filter_name) + if n then + return n, v, f + end + else + initmodule = initmodule or module .. ".init" + entries = tree.manifest.modules[initmodule] + if entries then + local n, v, f = add_providers(providers, entries, tree, initmodule, filter_name) + if n then + return n, v, f + end + end + end + end + + if next(providers) then + table.sort(providers, sort_versions) + local first = providers[1] + return first.name, first.version.string, first.module_name + end +end + + + + + + + + + + + + +local function filter_module_name(file_name, name, version, _tree, i) + if i > 1 then + file_name = path.versioned_name(file_name, "", name, version) + end + return path.path_to_module(file_name) +end + + + + + + + + + +local function pick_module(module) + return select_module(module, filter_module_name) +end + + + + + + + + + + + + + +function loader.which(module, where) + where = where or "l" + if where:match("l") then + local rock_name, rock_version, file_name = select_module(module, path.which_i) + if rock_name then + local fd = io.open(file_name) + if fd then + fd:close() + return file_name, rock_name, rock_version, "l" + end + end + end + if where:match("p") then + local modpath = module:gsub("%.", "/") + for _, v in ipairs({ package.path, package.cpath }) do + for p in v:gmatch("([^;]+)") do + local file_name = p:gsub("%?", modpath) + local fd = io.open(file_name) + if fd then + fd:close() + return file_name, v, nil, "p" + end + end + end + end +end + + + + + + + + + + + + + +function loader.luarocks_loader(module) + local name, version, module_name = pick_module(module) + if not name then + return "No LuaRocks module found for " .. module + else + loader.add_context(name, version) + return call_other_loaders(module, name, version, module_name) + end +end + +table.insert(loaders, 1, loader.luarocks_loader) + +if is_clean then + for modname, _ in pairs(package.loaded) do + if modname:match("^luarocks%.") then + package.loaded[modname] = nil + end + end +end + +return loader diff --git a/src/luarocks/manif.lua b/src/luarocks/manif.lua new file mode 100644 index 00000000..27144894 --- /dev/null +++ b/src/luarocks/manif.lua @@ -0,0 +1,229 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local assert = _tl_compat and _tl_compat.assert or assert; local io = _tl_compat and _tl_compat.io or io; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local pairs = _tl_compat and _tl_compat.pairs or pairs; local string = _tl_compat and _tl_compat.string or string + + + +local manif = {} + + + + + + + + + +local core = require("luarocks.core.manif") +local persist = require("luarocks.persist") +local fetch = require("luarocks.fetch") +local dir = require("luarocks.dir") +local fs = require("luarocks.fs") +local cfg = require("luarocks.core.cfg") +local path = require("luarocks.path") +local util = require("luarocks.util") +local queries = require("luarocks.queries") +local type_manifest = require("luarocks.type.manifest") + + + + + + +manif.cache_manifest = core.cache_manifest +manif.load_rocks_tree_manifests = core.load_rocks_tree_manifests +manif.scan_dependencies = core.scan_dependencies + +manif.rock_manifest_cache = {} + +local function check_manifest(repo_url, manifest, globals) + local ok, err = type_manifest.check(manifest, globals) + if not ok then + core.cache_manifest(repo_url, cfg.lua_version, nil) + return nil, "Error checking manifest: " .. err, "type" + end + return manifest +end + +local postprocess_dependencies +do + local postprocess_check = setmetatable({}, { __mode = "k" }) + postprocess_dependencies = function(manifest) + if postprocess_check[manifest] then + return + end + if manifest.dependencies then + for _, versions in pairs(manifest.dependencies) do + for _, entries in pairs(versions) do + for k, v in ipairs(entries) do + entries[k] = queries.from_persisted_table(v) + end + end + end + end + postprocess_check[manifest] = true + end +end + +function manif.load_rock_manifest(name, version, root) + assert(not name:match("/")) + + local name_version = name .. "/" .. version + if manif.rock_manifest_cache[name_version] then + return manif.rock_manifest_cache[name_version].rock_manifest + end + local pathname = path.rock_manifest_file(name, version, root) + local rock_manifest = persist.load_into_table(pathname) + if not rock_manifest then + return nil, "rock_manifest file not found for " .. name .. " " .. version .. " - not a LuaRocks tree?" + end + manif.rock_manifest_cache[name_version] = rock_manifest + return rock_manifest.rock_manifest +end + + + + + + + + + + +function manif.load_manifest(repo_url, lua_version, versioned_only) + lua_version = lua_version or cfg.lua_version + + local cached_manifest = core.get_cached_manifest(repo_url, lua_version) + if cached_manifest then + postprocess_dependencies(cached_manifest) + return cached_manifest + end + + local filenames = { + "manifest-" .. lua_version .. ".zip", + "manifest-" .. lua_version, + not versioned_only and "manifest" or nil, + } + + local protocol, repodir = dir.split_url(repo_url) + local pathname, from_cache + if protocol == "file" then + for _, filename in ipairs(filenames) do + pathname = dir.path(repodir, filename) + if fs.exists(pathname) then + break + end + end + else + local err, errcode + for _, filename in ipairs(filenames) do + pathname, err, errcode, from_cache = fetch.fetch_caching(dir.path(repo_url, filename), "no_mirror") + if pathname then + break + end + end + if not pathname then + return nil, err, errcode + end + end + if pathname:match(".*%.zip$") then + pathname = fs.absolute_name(pathname) + local nozip = pathname:match("(.*)%.zip$") + if not from_cache then + local dirname = dir.dir_name(pathname) + fs.change_dir(dirname) + fs.delete(nozip) + local ok, err = fs.unzip(pathname) + fs.pop_dir() + if not ok then + fs.delete(pathname) + fs.delete(pathname .. ".timestamp") + return nil, "Failed extracting manifest file: " .. err + end + end + pathname = nozip + end + local manifest, err, errcode = core.manifest_loader(pathname, repo_url, lua_version) + if not manifest and type(err) == "string" then + return nil, err, errcode + end + + postprocess_dependencies(manifest) + return check_manifest(repo_url, manifest, err) +end + + + + + +function manif.get_provided_item(deploy_type, file_path) + local item_type = deploy_type == "bin" and "command" or "module" + local item_name = item_type == "command" and file_path or path.path_to_module(file_path) + return item_type, item_name +end + +local function get_providers(item_type, item_name, repo) + local rocks_dir = path.rocks_dir(repo or cfg.root_dir) + local manifest = manif.load_manifest(rocks_dir) + return manifest and (manifest)[item_type .. "s"][item_name] +end + + + + + + + + +function manif.get_current_provider(item_type, item_name, repo) + local providers = get_providers(item_type, item_name, repo) + if providers then + return providers[1]:match("([^/]*)/([^/]*)") + end +end + +function manif.get_next_provider(item_type, item_name, repo) + local providers = get_providers(item_type, item_name, repo) + if providers and providers[2] then + return providers[2]:match("([^/]*)/([^/]*)") + end +end + + + + + + + + + +function manif.get_versions(dep, deps_mode) + + local name = dep.name + local namespace = dep.namespace + + local version_set = {} + path.map_trees(deps_mode, function(tree) + local manifest = manif.load_manifest(path.rocks_dir(tree)) + + if manifest and manifest.repository[name] then + for version in pairs(manifest.repository[name]) do + if dep.namespace then + local ns_file = path.rock_namespace_file(name, version, tree) + local fd = io.open(ns_file, "r") + if fd then + local ns = fd:read("*a") + fd:close() + if ns == namespace then + version_set[version] = tree + end + end + else + version_set[version] = tree + end + end + end + end) + + return util.keys(version_set), version_set +end + +return manif diff --git a/src/luarocks/manif/writer.lua b/src/luarocks/manif/writer.lua new file mode 100644 index 00000000..04becbac --- /dev/null +++ b/src/luarocks/manif/writer.lua @@ -0,0 +1,459 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local assert = _tl_compat and _tl_compat.assert or assert; local io = _tl_compat and _tl_compat.io or io; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local pairs = _tl_compat and _tl_compat.pairs or pairs; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table +local writer = {} + + +local cfg = require("luarocks.core.cfg") +local search = require("luarocks.search") +local repos = require("luarocks.repos") +local deps = require("luarocks.deps") +local vers = require("luarocks.core.vers") +local fs = require("luarocks.fs") +local util = require("luarocks.util") +local dir = require("luarocks.dir") +local fetch = require("luarocks.fetch") +local path = require("luarocks.path") +local persist = require("luarocks.persist") +local manif = require("luarocks.manif") +local queries = require("luarocks.queries") + + + + + + + + + + + + + + + + + + + +local function store_package_items(storage, name, version, items) + assert(not name:match("/")) + + local package_identifier = name .. "/" .. version + + for item_name, _ in pairs(items) do + if not storage[item_name] then + storage[item_name] = {} + end + + table.insert(storage[item_name], package_identifier) + end +end + + + + + + + + +local function remove_package_items(storage, name, version, items) + assert(not name:match("/")) + + local package_identifier = name .. "/" .. version + + for item_name, _path in pairs(items) do + local key = item_name + local all_identifiers = storage[key] + if not all_identifiers then + key = key .. ".init" + all_identifiers = storage[key] + end + + if all_identifiers then + for i, identifier in ipairs(all_identifiers) do + if identifier == package_identifier then + table.remove(all_identifiers, i) + break + end + end + + if #all_identifiers == 0 then + storage[key] = nil + end + else + util.warning("Cannot find entry for " .. item_name .. " in manifest -- corrupted manifest?") + end + end +end + + + + + + + + + +local function update_dependencies(manifest, deps_mode) + + if not manifest.dependencies then manifest.dependencies = {} end + local mdeps = manifest.dependencies + + for pkg, versions in pairs(manifest.repository) do + for version, repositories in pairs(versions) do + for _, repo in ipairs(repositories) do + if repo.arch == "installed" then + local rd = {} + repo.dependencies = rd + deps.scan_deps(rd, mdeps, pkg, version, deps_mode) + rd[pkg] = nil + end + end + end + end +end + + + + + + + + + + +local function sort_pkgs(a, b) + local na, va = a:match("(.*)/(.*)$") + local nb, vb = b:match("(.*)/(.*)$") + + return (na == nb) and vers.compare_versions(va, vb) or na < nb +end + + + + +local function sort_package_matching_table(tbl) + + if next(tbl) then + for item, pkgs in pairs(tbl) do + if #pkgs > 1 then + table.sort(pkgs, sort_pkgs) + + local prev = nil + local i = 1 + while pkgs[i] do + local curr = pkgs[i] + if curr == prev then + table.remove(pkgs, i) + else + prev = curr + i = i + 1 + end + end + end + end + end +end + + + + + + + +local function filter_by_lua_version(manifest, lua_version_str, repodir, cache) + + cache = cache or {} + local lua_version = vers.parse_version(lua_version_str) + for pkg, versions in pairs(manifest.repository) do + local to_remove = {} + for version, repositories in pairs(versions) do + for _, repo in ipairs(repositories) do + if repo.arch == "rockspec" then + local pathname = dir.path(repodir, pkg .. "-" .. version .. ".rockspec") + local rockspec = cache[pathname] + local err + if not rockspec then + rockspec, err = fetch.load_local_rockspec(pathname, true) + end + if rockspec then + cache[pathname] = rockspec + for _, dep in ipairs(rockspec.dependencies.queries) do + if dep.name == "lua" then + if not vers.match_constraints(lua_version, dep.constraints) then + table.insert(to_remove, version) + end + break + end + end + else + util.printerr("Error loading rockspec for " .. pkg .. " " .. version .. ": " .. err) + end + end + end + end + if next(to_remove) then + for _, incompat in ipairs(to_remove) do + versions[incompat] = nil + end + if not next(versions) then + manifest.repository[pkg] = nil + end + end + end +end + + + + + + +local function store_results(results, manifest) + + for name, versions in pairs(results) do + local pkgtable = manifest.repository[name] or {} + for version, entries in pairs(versions) do + local versiontable = {} + for _, entry in ipairs(entries) do + local entrytable = {} + entrytable.arch = entry.arch + if entry.arch == "installed" then + local rock_manifest, err = manif.load_rock_manifest(name, version) + if not rock_manifest then return nil, err end + + entrytable.modules = repos.package_modules(name, version) + store_package_items(manifest.modules, name, version, entrytable.modules) + entrytable.commands = repos.package_commands(name, version) + store_package_items(manifest.commands, name, version, entrytable.commands) + end + table.insert(versiontable, entrytable) + end + pkgtable[version] = versiontable + end + manifest.repository[name] = pkgtable + end + sort_package_matching_table(manifest.modules) + sort_package_matching_table(manifest.commands) + return true +end + + + + + + + +local function save_table(where, name, tbl) + assert(not name:match("/")) + + local filename = dir.path(where, name) + local ok, err = persist.save_from_table(filename .. ".tmp", tbl) + if ok then + ok, err = fs.replace_file(filename, filename .. ".tmp") + end + return ok, err +end + +function writer.make_rock_manifest(name, version) + local install_dir = path.install_dir(name, version) + local tree = {} + for _, file in ipairs(fs.find(install_dir)) do + local full_path = dir.path(install_dir, file) + local walk = tree + local last + local last_name + local next + for filename in file:gmatch("[^\\/]+") do + next = walk[filename] + if not next then + next = {} + walk[filename] = next + end + last = walk + last_name = filename + assert(type(next) == "table") + walk = next + end + if fs.is_file(full_path) then + + local sum, err = fs.get_md5(full_path) + if not sum then + return nil, "Failed producing checksum: " .. tostring(err) + end + last[last_name] = sum + end + end + local rock_manifest = { rock_manifest = tree } + manif.rock_manifest_cache[name .. "/" .. version] = rock_manifest + save_table(install_dir, "rock_manifest", rock_manifest) + return true +end + + + + + + + +function writer.make_namespace_file(name, version, namespace) + assert(not name:match("/")) + if not namespace then + return true + end + local fd, err = io.open(path.rock_namespace_file(name, version), "w") + if not fd then + return nil, err + end + local ok, err = fd:write(namespace) + if not ok then + return nil, err + end + fd:close() + return true +end + + + + + + + + + + + +function writer.make_manifest(repo, deps_mode, remote) + + if deps_mode == "none" then deps_mode = cfg.deps_mode end + + if not fs.is_dir(repo) then + return nil, "Cannot access repository at " .. repo + end + + local query = queries.all("any") + local results = search.disk_search(repo, query) + local manifest = { repository = {}, modules = {}, commands = {} } + + manif.cache_manifest(repo, nil, manifest) + + local ok, err = store_results(results, manifest) + if not ok then return nil, err end + + if remote then + local cache = {} + for luaver in util.lua_versions() do + local vmanifest = { repository = {}, modules = {}, commands = {} } + ok, err = store_results(results, vmanifest) + filter_by_lua_version(vmanifest, luaver, repo, cache) + if not cfg.no_manifest then + save_table(repo, "manifest-" .. luaver, vmanifest) + end + end + else + update_dependencies(manifest, deps_mode) + end + + if cfg.no_manifest then + + return true + end + return save_table(repo, "manifest", manifest) +end + + + + + + + + + + + + +function writer.add_to_manifest(name, version, repo, deps_mode) + assert(not name:match("/")) + local rocks_dir = path.rocks_dir(repo or cfg.root_dir) + + if deps_mode == "none" then deps_mode = cfg.deps_mode end + + local manifest, err = manif.load_manifest(rocks_dir) + if not manifest then + util.printerr("No existing manifest. Attempting to rebuild...") + + + + return writer.make_manifest(rocks_dir, deps_mode) + end + + local results = { [name] = { [version] = { { arch = "installed", repo = rocks_dir } } } } + + local ok + ok, err = store_results(results, manifest) + if not ok then return nil, err end + + update_dependencies(manifest, deps_mode) + + if cfg.no_manifest then + return true + end + return save_table(rocks_dir, "manifest", manifest) +end + + + + + + + + + + + + +function writer.remove_from_manifest(name, version, repo, deps_mode) + assert(not name:match("/")) + local rocks_dir = path.rocks_dir(repo or cfg.root_dir) + + if deps_mode == "none" then deps_mode = cfg.deps_mode end + + local manifest, err = manif.load_manifest(rocks_dir) + if not manifest then + util.printerr("No existing manifest. Attempting to rebuild...") + + + return writer.make_manifest(rocks_dir, deps_mode) + end + + local package_entry = manifest.repository[name] + if package_entry == nil or package_entry[version] == nil then + + return true + end + + local version_entry = package_entry[version][1] + if not version_entry then + + return writer.make_manifest(rocks_dir, deps_mode) + end + + remove_package_items(manifest.modules, name, version, version_entry.modules) + remove_package_items(manifest.commands, name, version, version_entry.commands) + + package_entry[version] = nil + manifest.dependencies[name][version] = nil + + if not next(package_entry) then + + manifest.repository[name] = nil + manifest.dependencies[name] = nil + end + + update_dependencies(manifest, deps_mode) + + if cfg.no_manifest then + return true + end + return save_table(rocks_dir, "manifest", manifest) +end + +return writer diff --git a/src/luarocks/pack.lua b/src/luarocks/pack.lua new file mode 100644 index 00000000..8a4df1a0 --- /dev/null +++ b/src/luarocks/pack.lua @@ -0,0 +1,188 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local pairs = _tl_compat and _tl_compat.pairs or pairs; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table; local _tl_table_unpack = unpack or table.unpack + +local pack = {} + + +local queries = require("luarocks.queries") +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") +local signing = require("luarocks.signing") + + + + + + + + + + + + + + +function pack.pack_source_rock(rockspec_file) + + local rockspec, errload = fetch.load_rockspec(rockspec_file) + if errload then + return nil, "Error loading rockspec: " .. errload + end + rockspec_file = rockspec.local_abs_filename + + local name_version = rockspec.name .. "-" .. rockspec.version + local rock_file = fs.absolute_name(name_version .. ".src.rock") + + local temp_dir, err = fs.make_temp_dir("pack-" .. name_version) + if not temp_dir then + return nil, "Failed creating temporary directory: " .. err + end + util.schedule_function(fs.delete, temp_dir) + + local source_file, source_dir = fetch.fetch_sources(rockspec, true, temp_dir) + if not source_file then + return nil, source_dir + end + local ok, errchange = fs.change_dir(source_dir) + if not ok then return nil, errchange end + + fs.delete(rock_file) + fs.copy(rockspec_file, source_dir, "read") + ok, err = fs.zip(rock_file, dir.base_name(rockspec_file), dir.base_name(source_file)) + if not ok then + return nil, "Failed packing " .. rock_file .. " - " .. err + 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 + 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 + + + + + + +function pack.pack_installed_rock(query, tree) + + local name, version, repo, repo_url = search.pick_installed_rock(query, tree) + if not name then + return nil, version + end + + local root = path.root_from_rocks_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(repo), dir.path(temp_dir, "lib"), "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(repo), dir.path(temp_dir, "lua"), "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) + ok, err = fs.zip(rock_file, _tl_table_unpack(fs.list_dir())) + if not ok then + return nil, "Failed packing " .. rock_file .. " - " .. err + end + fs.pop_dir() + fs.delete(temp_dir) + return rock_file +end + +function pack.report_and_sign_local_file(file, err, sign) + if err then + return nil, err + end + local sigfile + if sign then + sigfile, err = signing.sign_file(file) + util.printout() + end + util.printout("Packed: " .. file) + if sigfile then + util.printout("Signature stored in: " .. sigfile) + end + if err then + return nil, err + end + return true +end + +function pack.pack_binary_rock(name, namespace, version, sign, cmd) + + + + + + + + + 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 + local query = queries.new(rname, namespace, rversion) + local file, err = pack.pack_installed_rock(query, temp_dir) + return pack.report_and_sign_local_file(file, err, sign) +end + +return pack diff --git a/src/luarocks/path.lua b/src/luarocks/path.lua new file mode 100644 index 00000000..65c1a7d2 --- /dev/null +++ b/src/luarocks/path.lua @@ -0,0 +1,254 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local assert = _tl_compat and _tl_compat.assert or assert; local io = _tl_compat and _tl_compat.io or io; local package = _tl_compat and _tl_compat.package or package; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table + + + + +local cfg = require("luarocks.core.cfg") +local core = require("luarocks.core.path") +local dir = require("luarocks.dir") +local util = require("luarocks.core.util") + + + +local path = {} + + + + + + + + + +path.rocks_dir = core.rocks_dir +path.versioned_name = core.versioned_name +path.path_to_module = core.path_to_module +path.deploy_lua_dir = core.deploy_lua_dir +path.deploy_lib_dir = core.deploy_lib_dir +path.map_trees = core.map_trees +path.rocks_tree_to_string = core.rocks_tree_to_string + +function path.root_dir(tree) + if type(tree) == "string" then + return tree + else + return tree.root + end +end + + + + +function path.rockspec_name_from_rock(rock_name) + local base_name = dir.base_name(rock_name) + return base_name:match("(.*)%.[^.]*.rock") .. ".rockspec" +end + +function path.root_from_rocks_dir(rocks_dir) + return rocks_dir:match("(.*)" .. util.matchquote(cfg.rocks_subdir) .. ".*$") +end + +function path.deploy_bin_dir(tree) + return dir.path(path.root_dir(tree), "bin") +end + +function path.manifest_file(tree) + return dir.path(path.rocks_dir(tree), "manifest") +end + + + + + + +function path.versions_dir(name, tree) + assert(not name:match("/")) + return dir.path(path.rocks_dir(tree), name) +end + + + + + + + +function path.install_dir(name, version, tree) + assert(not name:match("/")) + return dir.path(path.rocks_dir(tree), name, version) +end + + + + + + + +function path.rockspec_file(name, version, tree) + assert(not name:match("/")) + return dir.path(path.rocks_dir(tree), name, version, name .. "-" .. version .. ".rockspec") +end + + + + + + + +function path.rock_manifest_file(name, version, tree) + assert(not name:match("/")) + return dir.path(path.rocks_dir(tree), name, version, "rock_manifest") +end + + + + + + + +function path.rock_namespace_file(name, version, tree) + assert(not name:match("/")) + return dir.path(path.rocks_dir(tree), name, version, "rock_namespace") +end + + + + + + + +function path.lib_dir(name, version, tree) + assert(not name:match("/")) + return dir.path(path.rocks_dir(tree), name, version, "lib") +end + + + + + + + +function path.lua_dir(name, version, tree) + assert(not name:match("/")) + return dir.path(path.rocks_dir(tree), name, version, "lua") +end + + + + + + + +function path.doc_dir(name, version, tree) + assert(not name:match("/")) + return dir.path(path.rocks_dir(tree), name, version, "doc") +end + + + + + + + +function path.conf_dir(name, version, tree) + assert(not name:match("/")) + return dir.path(path.rocks_dir(tree), name, version, "conf") +end + + + + + + + + +function path.bin_dir(name, version, tree) + assert(not name:match("/")) + return dir.path(path.rocks_dir(tree), name, version, "bin") +end + + + + + + +function path.parse_name(file_name) + if file_name:match("%.rock$") then + return dir.base_name(file_name):match("(.*)-([^-]+-%d+)%.([^.]+)%.rock$") + else + return dir.base_name(file_name):match("(.*)-([^-]+-%d+)%.(rockspec)") + end +end + + + + + + + +function path.make_url(pathname, name, version, arch) + assert(not name:match("/")) + local filename = name .. "-" .. version + if arch == "installed" then + filename = dir.path(name, version, filename .. ".rockspec") + elseif arch == "rockspec" then + filename = filename .. ".rockspec" + else + filename = filename .. "." .. arch .. ".rock" + end + return dir.path(pathname, filename) +end + + + + + +function path.module_to_path(mod) + return (mod:gsub("[^.]*$", ""):gsub("%.", "/")) +end + +function path.use_tree(tree) + cfg.root_dir = tree + cfg.rocks_dir = path.rocks_dir(tree) + cfg.deploy_bin_dir = path.deploy_bin_dir(tree) + cfg.deploy_lua_dir = path.deploy_lua_dir(tree) + cfg.deploy_lib_dir = path.deploy_lib_dir(tree) +end + +function path.add_to_package_paths(tree) + package.path = dir.path(path.deploy_lua_dir(tree), "?.lua") .. ";" .. + dir.path(path.deploy_lua_dir(tree), "?/init.lua") .. ";" .. + package.path + package.cpath = dir.path(path.deploy_lib_dir(tree), "?." .. cfg.lib_extension) .. ";" .. + package.cpath +end + + + + + + +function path.read_namespace(name, version, tree) + assert(not name:match("/")) + + local namespace + local fd = io.open(path.rock_namespace_file(name, version, tree), "r") + if fd then + namespace = fd:read("*a") + fd:close() + end + return namespace +end + +function path.package_paths(deps_mode) + local lpaths = {} + local lcpaths = {} + path.map_trees(deps_mode, function(tree) + local root = path.root_dir(tree) + table.insert(lpaths, dir.path(root, cfg.lua_modules_path, "?.lua")) + table.insert(lpaths, dir.path(root, cfg.lua_modules_path, "?/init.lua")) + table.insert(lcpaths, dir.path(root, cfg.lib_modules_path, "?." .. cfg.lib_extension)) + end) + return table.concat(lpaths, ";"), table.concat(lcpaths, ";") +end + +return path diff --git a/src/luarocks/persist.lua b/src/luarocks/persist.lua new file mode 100644 index 00000000..34ea2a52 --- /dev/null +++ b/src/luarocks/persist.lua @@ -0,0 +1,275 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local io = _tl_compat and _tl_compat.io or io; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table + + +local persist = {} + + + + + + + + + +local core = require("luarocks.core.persist") +local util = require("luarocks.util") +local dir = require("luarocks.dir") +local fs = require("luarocks.fs") +local cfg = require("luarocks.core.cfg") + + + + + + + + + +persist.run_file = core.run_file +persist.load_into_table = core.load_into_table + +local write_table + + + + + + + + + +function persist.write_value(out, v, level, sub_order) + if type(v) == "table" then + level = level or 0 + write_table(out, v, level + 1, sub_order) + elseif type(v) == "string" then + if v:match("[\r\n]") then + local open, close = "[[", "]]" + local equals = 0 + local v_with_bracket = v .. "]" + while v_with_bracket:find(close, 1, true) do + equals = equals + 1 + local eqs = ("="):rep(equals) + open, close = "[" .. eqs .. "[", "]" .. eqs .. "]" + end + out:write(open .. "\n" .. v .. close) + else + out:write(("%q"):format(v)) + end + else + out:write(tostring(v)) + end +end + +local is_valid_plain_key +do + local keywords = { + ["and"] = true, + ["break"] = true, + ["do"] = true, + ["else"] = true, + ["elseif"] = true, + ["end"] = true, + ["false"] = true, + ["for"] = true, + ["function"] = true, + ["goto"] = true, + ["if"] = true, + ["in"] = true, + ["local"] = true, + ["nil"] = true, + ["not"] = true, + ["or"] = true, + ["repeat"] = true, + ["return"] = true, + ["then"] = true, + ["true"] = true, + ["until"] = true, + ["while"] = true, + } + function is_valid_plain_key(k) + return k:match("^[a-zA-Z_][a-zA-Z0-9_]*$") and + not keywords[k] + end +end + +local function write_table_key_assignment(out, k, level) + if type(k) == "string" and is_valid_plain_key(k) then + out:write(k) + else + out:write("[") + persist.write_value(out, k, level) + out:write("]") + end + + out:write(" = ") +end + + + + + + + + +write_table = function(out, tbl, level, sort_by) + out:write("{") + local sep = "\n" + local indentation = " " + local indent = true + local i = 1 + for k, v, sub_order in util.sortedpairs(tbl, sort_by) do + out:write(sep) + if indent then + for _ = 1, level do out:write(indentation) end + end + + if type(k) == "number" then + i = i + 1 + else + write_table_key_assignment(out, k, level) + end + + persist.write_value(out, v, level, sub_order) + if type(v) == "number" then + sep = ", " + indent = false + else + sep = ",\n" + indent = true + end + end + if sep ~= "\n" then + out:write("\n") + for _ = 1, level - 1 do out:write(indentation) end + end + out:write("}") +end + + + + + + +local function write_table_as_assignments(out, tbl, sort_by) + for k, v, sub_order in util.sortedpairs(tbl, sort_by) do + if not (type(k) == "string" and is_valid_plain_key(k)) then + return nil, "cannot store '" .. tostring(k) .. "' as a plain key." + end + out:write(k .. " = ") + persist.write_value(out, v, 0, sub_order) + out:write("\n") + end + return true +end + + + + +local function write_table_as_table(out, tbl) + out:write("return {\n") + for k, v, sub_order in util.sortedpairs(tbl) do + out:write(" ") + write_table_key_assignment(out, k, 1) + persist.write_value(out, v, 1, sub_order) + out:write(",\n") + end + out:write("}\n") +end + + + + + + + + +function persist.save_from_table_to_string(tbl, sort_by) + local out = { buffer = {} } + function out:write(data) table.insert(self.buffer, data) end + local ok, err = write_table_as_assignments(out, tbl, sort_by) + if not ok then + return nil, err + end + return table.concat(out.buffer) +end + + + + + + + + + + +function persist.save_from_table(filename, tbl, sort_by) + local prefix = dir.dir_name(filename) + fs.make_dir(prefix) + local out = io.open(filename, "w") + if not out then + return nil, "Cannot create file at " .. filename + end + local ok, err = write_table_as_assignments(out, tbl, sort_by) + out:close() + if not ok then + return nil, err + end + return true +end + + + + + + + + + +function persist.save_as_module(filename, tbl) + local out = io.open(filename, "w") + if not out then + return nil, "Cannot create file at " .. filename + end + write_table_as_table(out, tbl) + out:close() + return true +end + +function persist.load_config_file_if_basic(filename, config) + local env = { + home = config.home, + } + local result, _, errcode = persist.load_into_table(filename, env) + if errcode == "load" or errcode == "run" then + + return nil, "Could not read existing config file " .. filename + end + + local tbl + if errcode == "open" then + + tbl = {} + else + tbl = result + tbl.home = nil + end + + return tbl +end + +function persist.save_default_lua_version(prefix, lua_version) + local ok, err_makedir = fs.make_dir(prefix) + if not ok then + return nil, err_makedir + end + local fd, err_open = io.open(dir.path(prefix, "default-lua-version.lua"), "w") + if not fd then + return nil, err_open + end + fd:write('return "' .. lua_version .. '"\n') + fd:close() + return true +end + +return persist diff --git a/src/luarocks/queries.lua b/src/luarocks/queries.lua new file mode 100644 index 00000000..34b6d687 --- /dev/null +++ b/src/luarocks/queries.lua @@ -0,0 +1,211 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table +local queries = {} + + +local vers = require("luarocks.core.vers") +local util = require("luarocks.util") +local cfg = require("luarocks.core.cfg") + +local query = require("luarocks.core.types.query") + + + + + +local query_mt = {} + +query_mt.__index = query.Query + + +query.Query.arch = { + src = true, + all = true, + rockspec = true, + installed = true, + +} + + +query.Query.substring = false + + + +local function arch_to_table(input) + if type(input) == "table" then + return input + elseif type(input) == "string" then + local arch = {} + for a in input:gmatch("[%w_-]+") do + arch[a] = true + end + return arch + end +end + + + + + + + + + + +function queries.new(name, namespace, version, substring, arch, operator) + + operator = operator or "==" + + local self = { + name = name, + namespace = namespace, + constraints = {}, + substring = substring, + arch = arch_to_table(arch), + } + if version then + table.insert(self.constraints, { op = operator, version = vers.parse_version(version) }) + end + + query.Query.arch[cfg.arch] = true + return setmetatable(self, query_mt) +end + + + +function queries.all(arch) + + return queries.new("", nil, nil, true, arch) +end + +do + local parse_constraints + do + local parse_constraint + do + local operators = { + ["=="] = "==", + ["~="] = "~=", + [">"] = ">", + ["<"] = "<", + [">="] = ">=", + ["<="] = "<=", + ["~>"] = "~>", + + [""] = "==", + ["="] = "==", + ["!="] = "~=", + } + + + + + + + + + + parse_constraint = function(input) + + local no_upgrade, op, versionstr, rest = input:match("^(@?)([<>=~!]*)%s*([%w%.%_%-]+)[%s,]*(.*)") + local _op = operators[op] + local version = vers.parse_version(versionstr) + if not _op then + return nil, "Encountered bad constraint operator: '" .. tostring(op) .. "' in '" .. input .. "'" + end + if not version then + return nil, "Could not parse version from constraint: '" .. input .. "'" + end + return { op = _op, version = version, no_upgrade = no_upgrade == "@" and true or nil }, rest + end + end + + + + + + + + + + parse_constraints = function(input) + + local constraints, oinput = {}, input + local constraint + while #input > 0 do + constraint, input = parse_constraint(input) + if constraint then + table.insert(constraints, constraint) + else + return nil, "Failed to parse constraint '" .. tostring(oinput) .. "' with error: " .. input + end + end + return constraints + end + end + + + + + + function queries.from_dep_string(depstr) + + local ns_name, rest = depstr:match("^%s*([a-zA-Z0-9%.%-%_]*/?[a-zA-Z0-9][a-zA-Z0-9%.%-%_]*)%s*([^/]*)") + if not ns_name then + return nil, "failed to extract dependency name from '" .. depstr .. "'" + end + + ns_name = ns_name:lower() + + local constraints, err = parse_constraints(rest) + if not constraints then + return nil, err + end + + local name, namespace = util.split_namespace(ns_name) + + local self = { + name = name, + namespace = namespace, + constraints = constraints, + } + + query.Query.arch[cfg.arch] = true + return setmetatable(self, query_mt) + end +end + +function queries.from_persisted_table(tbl) + query.Query.arch[cfg.arch] = true + return setmetatable(tbl, query_mt) +end + + + + + +function query_mt.__tostring(self) + local out = {} + if self.namespace then + table.insert(out, self.namespace) + table.insert(out, "/") + end + table.insert(out, self.name) + + if #self.constraints > 0 then + local pretty = {} + for _, c in ipairs(self.constraints) do + local v = tostring(c.version) + if c.op == "==" then + table.insert(pretty, v) + else + table.insert(pretty, c.op .. " " .. v) + end + end + table.insert(out, " ") + table.insert(out, table.concat(pretty, ", ")) + end + + return table.concat(out) +end + +return queries diff --git a/src/luarocks/remove.lua b/src/luarocks/remove.lua new file mode 100644 index 00000000..72d3a1d8 --- /dev/null +++ b/src/luarocks/remove.lua @@ -0,0 +1,140 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local pairs = _tl_compat and _tl_compat.pairs or pairs; local table = _tl_compat and _tl_compat.table or table +local remove = {} + + +local search = require("luarocks.search") +local deps = require("luarocks.deps") +local fetch = require("luarocks.fetch") +local repos = require("luarocks.repos") +local repo_writer = require("luarocks.repo_writer") +local path = require("luarocks.path") +local util = require("luarocks.util") +local cfg = require("luarocks.core.cfg") +local manif = require("luarocks.manif") +local queries = require("luarocks.queries") + + + + + + + + + +local function check_dependents(name, versions, deps_mode) + local dependents = {} + + local skip_set = {} + skip_set[name] = {} + for version, _ in pairs(versions) do + skip_set[name][version] = true + end + + local local_rocks = {} + local query_all = queries.all() + search.local_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.dependencies.queries, rockspec.rocks_provided, deps_mode, skip_set) + if missing[name] then + table.insert(dependents, { name = rock_name, version = rock_version }) + end + end + end + end + + return dependents +end + + + + + + + + +local function delete_versions(name, versions, deps_mode) + + for version, _ in pairs(versions) do + util.printout("Removing " .. name .. " " .. version .. "...") + local ok, err = repo_writer.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 = {} + local query = queries.new(name, nil, version, false, nil, "~=") + search.local_manifest_search(results, cfg.rocks_dir, query) + local warn + if results[name] then + local ok, err = remove.remove_search_results(results, name, cfg.deps_mode, force, fast) + if not ok then + warn = err + end + end + + if not fast then + + + local rock_manifest, load_err = manif.load_rock_manifest(name, version) + local ok, err = repos.check_everything_is_installed(name, version, rock_manifest, cfg.root_dir, false) + if not ok then + return nil, err + end + end + + return true, nil, warn +end + +return remove diff --git a/src/luarocks/repo_writer.lua b/src/luarocks/repo_writer.lua new file mode 100644 index 00000000..0ccb3e91 --- /dev/null +++ b/src/luarocks/repo_writer.lua @@ -0,0 +1,52 @@ +local repo_writer = {} + + +local fs = require("luarocks.fs") +local path = require("luarocks.path") +local repos = require("luarocks.repos") +local writer = require("luarocks.manif.writer") + +function repo_writer.deploy_files(name, version, wrap_bin_scripts, deps_mode, namespace) + local ok, err + + 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 namespace then + ok, err = writer.make_namespace_file(name, version, namespace) + if not ok then + return nil, err + end + end + + ok, err = repos.deploy_local_files(name, version, wrap_bin_scripts, deps_mode) + if not ok then + return nil, err + end + + ok, err = writer.add_to_manifest(name, version, nil, deps_mode) + return ok, err +end + +function repo_writer.delete_version(name, version, deps_mode, quick) + local ok, err, op = repos.delete_local_version(name, version, deps_mode, quick) + + if op == "remove" then + local rok, rerr = writer.remove_from_manifest(name, version, nil, deps_mode) + if ok and not rok then + ok, err = rok, rerr + end + end + + return ok, err +end + +function repo_writer.refresh_manifest(rocks_dir) + return writer.make_manifest(rocks_dir, "one") +end + +return repo_writer diff --git a/src/luarocks/repos.lua b/src/luarocks/repos.lua new file mode 100644 index 00000000..84c01815 --- /dev/null +++ b/src/luarocks/repos.lua @@ -0,0 +1,690 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local assert = _tl_compat and _tl_compat.assert or assert; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local os = _tl_compat and _tl_compat.os or os; local pairs = _tl_compat and _tl_compat.pairs or pairs; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table + +local repos = {Op = {}, Paths = {}, } + + + + + + + + + + + + + + + +local fs = require("luarocks.fs") +local path = require("luarocks.path") +local cfg = require("luarocks.core.cfg") +local util = require("luarocks.util") +local dir = require("luarocks.dir") +local manif = require("luarocks.manif") +local vers = require("luarocks.core.vers") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +local function get_installed_versions(name) + assert(not name:match("/")) + + local dirs = fs.list_dir(path.versions_dir(name)) + return (dirs and #dirs > 0) and dirs or nil +end + + + + + + + +function repos.is_installed(name, version) + assert(not name:match("/")) + + return fs.is_dir(path.install_dir(name, version)) +end + +function repos.recurse_rock_manifest_entry(entry, action) + if entry == nil then + return true + end + + local function do_recurse_rock_manifest_entry(tree, parent_path) + + for file, sub in pairs(tree) do + local sub_path = (parent_path and (parent_path .. "/") or "") .. file + local ok, err + + if type(sub) == "table" then + ok, err = do_recurse_rock_manifest_entry(sub, sub_path) + else + ok, err = action(sub_path) + end + + if err then return nil, err end + end + return true + end + return do_recurse_rock_manifest_entry(entry) +end + +local function store_package_data(result, rock_manifest, deploy_type) + if rock_manifest[deploy_type] then + repos.recurse_rock_manifest_entry(rock_manifest[deploy_type], function(file_path) + local _, item_name = manif.get_provided_item(deploy_type, file_path) + result[item_name] = file_path + return true + end) + end +end + + + + + + + + + + +function repos.package_modules(name, version) + assert(not name:match("/")) + + local result = {} + local rock_manifest = manif.load_rock_manifest(name, version) + if not rock_manifest then return result end + store_package_data(result, rock_manifest, "lib") + store_package_data(result, rock_manifest, "lua") + return result +end + + + + + + + + + + +function repos.package_commands(name, version) + assert(not name:match("/")) + + local result = {} + local rock_manifest = manif.load_rock_manifest(name, version) + if not rock_manifest then return result end + store_package_data(result, rock_manifest, "bin") + return result +end + + + + + + + +function repos.has_binaries(name, version) + assert(not name:match("/")) + + local entries = manif.load_rock_manifest(name, version) + if not entries then + return false + end + local bin = entries["bin"] + if type(bin) == "table" then + for bin_name, md5 in pairs(bin) do + + if fs.is_actual_binary(dir.path(cfg.deploy_bin_dir, bin_name)) then + return true + end + end + end + return false +end + +function repos.run_hook(rockspec, hook_name) + + local hooks = rockspec.hooks + if not hooks then + return true + end + + if cfg.hooks_enabled == false then + return nil, "This rockspec contains hooks, which are blocked by the 'hooks_enabled' setting in your LuaRocks configuration." + end + + if not hooks.substituted_variables then + util.variable_substitutions(hooks, rockspec.variables) + hooks.substituted_variables = true + end + local hook = (hooks)[hook_name] + if hook then + util.printout(hook) + if not fs.execute(hook) then + return nil, "Failed running " .. hook_name .. " hook." + end + end + return true +end + +function repos.should_wrap_bin_scripts(rockspec) + + if cfg.wrap_bin_scripts then + return cfg.wrap_bin_scripts + end + if rockspec.deploy and rockspec.deploy.wrap_bin_scripts == false then + return false + end + return true +end + +local function find_suffixed(file, suffix) + local filenames = { file } + if suffix and suffix ~= "" then + table.insert(filenames, 1, file .. suffix) + end + + for _, filename in ipairs(filenames) do + if fs.exists(filename) then + return filename + end + end + + return nil, table.concat(filenames, ", ") .. " not found" +end + +local function check_suffix(filename, suffix) + local suffixed_filename, err = find_suffixed(filename, suffix) + if not suffixed_filename then + return "" + end + return suffixed_filename:sub(#filename + 1) +end + + + + + + + +local function get_deploy_paths(name, version, deploy_type, file_path, repo) + + repo = repo or cfg.root_dir + local deploy_dir = (path)["deploy_" .. deploy_type .. "_dir"](repo) + local non_versioned = dir.path(deploy_dir, file_path) + local versioned = path.versioned_name(non_versioned, deploy_dir, name, version) + return { nv = non_versioned, v = versioned } +end + +local function check_spot_if_available(name, version, deploy_type, file_path) + local item_type, item_name = manif.get_provided_item(deploy_type, file_path) + local cur_name, cur_version = manif.get_current_provider(item_type, item_name) + + + + + if not cur_name and deploy_type == "lua" and item_name:match("%.init$") then + cur_name, cur_version = manif.get_current_provider(item_type, (item_name:gsub("%.init$", ""))) + end + + if (not cur_name) or + (name < cur_name) or + (name == cur_name and (version == cur_version or + vers.compare_versions(version, cur_version))) then + return "nv", cur_name, cur_version, item_name + else + + return "v", cur_name, cur_version, item_name + end +end + +local function backup_existing(should_backup, target) + if not should_backup then + fs.delete(target) + return + end + if fs.exists(target) then + local backup = target + repeat + backup = backup .. "~" + until not fs.exists(backup) + + util.warning(target .. " is not tracked by this installation of LuaRocks. Moving it to " .. backup) + local move_ok, move_err = os.rename(target, backup) + if not move_ok then + return nil, move_err + end + return backup + end +end + +local function prepare_op_install() + local mkdirs = {} + local rmdirs = {} + + local function memoize_mkdir(d) + if mkdirs[d] then + return true + end + local ok, err = fs.make_dir(d) + if not ok then + return nil, err + end + mkdirs[d] = true + return true + end + + local function op_install(op) + local ok, err = memoize_mkdir(dir.dir_name(op.dst)) + if not ok then + return nil, err + end + + local backup, err = backup_existing(op.backup, op.dst) + if err then + return nil, err + end + if backup then + op.backup_file = backup + end + + ok, err = op.fn(op.src, op.dst, op.backup) + if not ok then + return nil, err + end + + rmdirs[dir.dir_name(op.src)] = true + return true + end + + local function done_op_install() + for d, _ in pairs(rmdirs) do + fs.remove_dir_tree_if_empty(d) + end + end + + return op_install, done_op_install +end + +local function rollback_install(op) + fs.delete(op.dst) + if op.backup_file then + os.rename(op.backup_file, op.dst) + end + fs.remove_dir_tree_if_empty(dir.dir_name(op.dst)) + return true +end + +local function op_rename(op) + if op.suffix then + local suffix = check_suffix(op.src, op.suffix) + op.src = op.src .. suffix + op.dst = op.dst .. suffix + end + + if fs.exists(op.src) then + fs.make_dir(dir.dir_name(op.dst)) + fs.delete(op.dst) + local ok, err = os.rename(op.src, op.dst) + fs.remove_dir_tree_if_empty(dir.dir_name(op.src)) + return ok, err + else + return true + end +end + +local function rollback_rename(op) + return op_rename({ src = op.dst, dst = op.src }) +end + +local function prepare_op_delete() + local deletes = {} + local rmdirs = {} + + local function done_op_delete() + for _, f in ipairs(deletes) do + os.remove(f) + end + + for d, _ in pairs(rmdirs) do + fs.remove_dir_tree_if_empty(d) + end + end + + local function op_delete(op) + if op.suffix then + local suffix = check_suffix(op.name, op.suffix) + op.name = op.name .. suffix + end + + table.insert(deletes, op.name) + + rmdirs[dir.dir_name(op.name)] = true + end + + return op_delete, done_op_delete +end + +local function rollback_ops(ops, op_fn, n) + for i = 1, n do + op_fn(ops[i]) + end +end + + +function repos.check_everything_is_installed(name, version, rock_manifest, repo, accept_versioned) + local missing = {} + local suffix = cfg.wrapper_suffix or "" + for _, category in ipairs({ "bin", "lua", "lib" }) do + if rock_manifest[category] then + repos.recurse_rock_manifest_entry(rock_manifest[category], function(file_path) + local paths = get_deploy_paths(name, version, category, file_path, repo) + if category == "bin" then + if (fs.exists(paths.nv) or fs.exists(paths.nv .. suffix)) or + (accept_versioned and (fs.exists(paths.v) or fs.exists(paths.v .. suffix))) then + return + end + else + if fs.exists(paths.nv) or (accept_versioned and fs.exists(paths.v)) then + return + end + end + table.insert(missing, paths.nv) + end) + end + end + if #missing > 0 then + return nil, "failed deploying files. " .. + "The following files were not installed:\n" .. + table.concat(missing, "\n") + end + return true +end + + + + + + + + +function repos.deploy_local_files(name, version, wrap_bin_scripts, deps_mode) + assert(not name:match("/")) + + local rock_manifest, load_err = manif.load_rock_manifest(name, version) + if not rock_manifest then return nil, load_err end + + local repo = cfg.root_dir + local renames = {} + local installs = {} + + local function install_binary(source, target) + if wrap_bin_scripts and fs.is_lua(source) then + return fs.wrap_script(source, target, deps_mode, name, version) + else + return fs.copy_binary(source, target) + end + end + + local function move_lua(source, target) + return fs.move(source, target, "read") + end + + local function move_lib(source, target) + return fs.move(source, target, "exec") + end + + if rock_manifest.bin then + local source_dir = path.bin_dir(name, version) + repos.recurse_rock_manifest_entry(rock_manifest.bin, function(file_path) + local source = dir.path(source_dir, file_path) + local paths = get_deploy_paths(name, version, "bin", file_path, repo) + local mode, cur_name, cur_version = check_spot_if_available(name, version, "bin", file_path) + + if mode == "nv" and cur_name then + local cur_paths = get_deploy_paths(cur_name, cur_version, "bin", file_path, repo) + table.insert(renames, { src = cur_paths.nv, dst = cur_paths.v, suffix = cfg.wrapper_suffix }) + end + local target = mode == "nv" and paths.nv or paths.v + local backup = name ~= cur_name or version ~= cur_version + if wrap_bin_scripts and fs.is_lua(source) then + target = target .. (cfg.wrapper_suffix or "") + end + table.insert(installs, { fn = install_binary, src = source, dst = target, backup = backup }) + end) + end + + if rock_manifest.lua then + local source_dir = path.lua_dir(name, version) + repos.recurse_rock_manifest_entry(rock_manifest.lua, function(file_path) + local source = dir.path(source_dir, file_path) + local paths = get_deploy_paths(name, version, "lua", file_path, repo) + local mode, cur_name, cur_version = check_spot_if_available(name, version, "lua", file_path) + + if mode == "nv" and cur_name then + local cur_paths = get_deploy_paths(cur_name, cur_version, "lua", file_path, repo) + table.insert(renames, { src = cur_paths.nv, dst = cur_paths.v }) + cur_paths = get_deploy_paths(cur_name, cur_version, "lib", file_path:gsub("%.lua$", "." .. cfg.lib_extension), repo) + table.insert(renames, { src = cur_paths.nv, dst = cur_paths.v }) + end + local target = mode == "nv" and paths.nv or paths.v + local backup = name ~= cur_name or version ~= cur_version + table.insert(installs, { fn = move_lua, src = source, dst = target, backup = backup }) + end) + end + + if rock_manifest.lib then + local source_dir = path.lib_dir(name, version) + repos.recurse_rock_manifest_entry(rock_manifest.lib, function(file_path) + local source = dir.path(source_dir, file_path) + local paths = get_deploy_paths(name, version, "lib", file_path, repo) + local mode, cur_name, cur_version = check_spot_if_available(name, version, "lib", file_path) + + if mode == "nv" and cur_name then + local cur_paths = get_deploy_paths(cur_name, cur_version, "lua", file_path:gsub("%.[^.]+$", ".lua"), repo) + table.insert(renames, { src = cur_paths.nv, dst = cur_paths.v }) + cur_paths = get_deploy_paths(cur_name, cur_version, "lib", file_path, repo) + table.insert(renames, { src = cur_paths.nv, dst = cur_paths.v }) + end + local target = mode == "nv" and paths.nv or paths.v + local backup = name ~= cur_name or version ~= cur_version + table.insert(installs, { fn = move_lib, src = source, dst = target, backup = backup }) + end) + end + + for i, op in ipairs(renames) do + local ok, err = op_rename(op) + if not ok then + rollback_ops(renames, rollback_rename, i - 1) + return nil, err + end + end + local op_install, done_op_install = prepare_op_install() + for i, op in ipairs(installs) do + local ok, err = op_install(op) + if not ok then + rollback_ops(installs, rollback_install, i - 1) + rollback_ops(renames, rollback_rename, #renames) + return nil, err + end + end + done_op_install() + + local ok, err = repos.check_everything_is_installed(name, version, rock_manifest, repo, true) + if not ok then + return nil, err + end + + return true +end + +local function add_to_double_checks(double_checks, name, version) + double_checks[name] = double_checks[name] or {} + double_checks[name][version] = true +end + +local function double_check_all(double_checks, repo) + local errs = {} + for next_name, versions in pairs(double_checks) do + for next_version in pairs(versions) do + local rock_manifest, load_err = manif.load_rock_manifest(next_name, next_version) + local ok, err = repos.check_everything_is_installed(next_name, next_version, rock_manifest, repo, true) + if not ok then + table.insert(errs, err) + end + end + end + if next(errs) then + return nil, table.concat(errs, "\n") + end + return true +end + + + + + + + + + + + +function repos.delete_local_version(name, version, deps_mode, quick) + assert(not name:match("/")) + + local rock_manifest, load_err = manif.load_rock_manifest(name, version) + if not rock_manifest then + if not quick then + return nil, "rock_manifest file not found for " .. name .. " " .. version .. " - removed entry from the manifest", "remove" + end + return nil, load_err, "fail" + end + + local repo = cfg.root_dir + local renames = {} + local deletes = {} + + local double_checks = {} + + if rock_manifest.bin then + repos.recurse_rock_manifest_entry(rock_manifest.bin, function(file_path) + local paths = get_deploy_paths(name, version, "bin", file_path, repo) + local mode, cur_name, cur_version, item_name = check_spot_if_available(name, version, "bin", file_path) + if mode == "v" then + table.insert(deletes, { name = paths.v, suffix = cfg.wrapper_suffix }) + else + table.insert(deletes, { name = paths.nv, suffix = cfg.wrapper_suffix }) + + local next_name, next_version = manif.get_next_provider("command", item_name) + if next_name then + add_to_double_checks(double_checks, next_name, next_version) + local next_paths = get_deploy_paths(next_name, next_version, "bin", file_path, repo) + table.insert(renames, { src = next_paths.v, dst = next_paths.nv, suffix = cfg.wrapper_suffix }) + end + end + end) + end + + if rock_manifest.lua then + repos.recurse_rock_manifest_entry(rock_manifest.lua, function(file_path) + local paths = get_deploy_paths(name, version, "lua", file_path, repo) + local mode, cur_name, cur_version, item_name = check_spot_if_available(name, version, "lua", file_path) + if mode == "v" then + table.insert(deletes, { name = paths.v }) + else + table.insert(deletes, { name = paths.nv }) + + local next_name, next_version = manif.get_next_provider("module", item_name) + if next_name then + add_to_double_checks(double_checks, next_name, next_version) + local next_lua_paths = get_deploy_paths(next_name, next_version, "lua", file_path, repo) + table.insert(renames, { src = next_lua_paths.v, dst = next_lua_paths.nv }) + local next_lib_paths = get_deploy_paths(next_name, next_version, "lib", file_path:gsub("%.[^.]+$", ".lua"), repo) + table.insert(renames, { src = next_lib_paths.v, dst = next_lib_paths.nv }) + end + end + end) + end + + if rock_manifest.lib then + repos.recurse_rock_manifest_entry(rock_manifest.lib, function(file_path) + local paths = get_deploy_paths(name, version, "lib", file_path, repo) + local mode, cur_name, cur_version, item_name = check_spot_if_available(name, version, "lib", file_path) + if mode == "v" then + table.insert(deletes, { name = paths.v }) + else + table.insert(deletes, { name = paths.nv }) + + local next_name, next_version = manif.get_next_provider("module", item_name) + if next_name then + add_to_double_checks(double_checks, next_name, next_version) + local next_lua_paths = get_deploy_paths(next_name, next_version, "lua", file_path:gsub("%.[^.]+$", ".lua"), repo) + table.insert(renames, { src = next_lua_paths.v, dst = next_lua_paths.nv }) + local next_lib_paths = get_deploy_paths(next_name, next_version, "lib", file_path, repo) + table.insert(renames, { src = next_lib_paths.v, dst = next_lib_paths.nv }) + end + end + end) + end + + local op_delete, done_op_delete = prepare_op_delete() + for _, op in ipairs(deletes) do + op_delete(op) + end + done_op_delete() + + if not quick then + for _, op in ipairs(renames) do + op_rename(op) + end + + local ok, err = double_check_all(double_checks, repo) + if not ok then + return nil, err, "fail" + end + end + + fs.delete(path.install_dir(name, version)) + if not get_installed_versions(name) then + fs.delete(dir.path(cfg.rocks_dir, name)) + end + + if quick then + return true, nil, "ok" + end + + return true, nil, "remove" +end + +return repos diff --git a/src/luarocks/require.lua b/src/luarocks/require.lua new file mode 100644 index 00000000..5b7ca3c3 --- /dev/null +++ b/src/luarocks/require.lua @@ -0,0 +1,2 @@ + +return require("luarocks.loader") diff --git a/src/luarocks/results.lua b/src/luarocks/results.lua new file mode 100644 index 00000000..fec1e463 --- /dev/null +++ b/src/luarocks/results.lua @@ -0,0 +1,60 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local assert = _tl_compat and _tl_compat.assert or assert; local string = _tl_compat and _tl_compat.string or string; local results = {} + + +local vers = require("luarocks.core.vers") +local util = require("luarocks.util") + + +local result = require("luarocks.core.types.result") + + +local result_mt = {} + +result_mt.__index = result.Result + +function results.new(name, version, repo, arch, namespace) + + assert(not name:match("/")) + + + if not namespace then + name, namespace = util.split_namespace(name) + end + + local self = { + name = name, + version = version, + namespace = namespace, + arch = arch, + repo = repo, + } + + return setmetatable(self, result_mt) +end + + + + + + + + +local function match_name(query, name) + if query.substring then + return name:find(query.name, 0, true) and true or false + else + return name == query.name + end +end + + + + +function result.Result:satisfies(query) + return match_name(query, self.name) and + (query.arch[self.arch] or query.arch["any"]) and + ((not query.namespace) or (query.namespace == self.namespace)) and + (vers.match_constraints(vers.parse_version(self.version), query.constraints)) +end + +return results diff --git a/src/luarocks/rockspecs.lua b/src/luarocks/rockspecs.lua new file mode 100644 index 00000000..c41469fd --- /dev/null +++ b/src/luarocks/rockspecs.lua @@ -0,0 +1,184 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local pairs = _tl_compat and _tl_compat.pairs or pairs; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table +local rockspecs = {} + + +local cfg = require("luarocks.core.cfg") +local dir = require("luarocks.dir") +local path = require("luarocks.path") +local queries = require("luarocks.queries") +local type_rockspec = require("luarocks.type.rockspec") +local util = require("luarocks.util") +local vers = require("luarocks.core.vers") + + + + + + +local vendored_build_type_set = { + ["builtin"] = true, + ["cmake"] = true, + ["command"] = true, + ["make"] = true, + ["module"] = true, + ["none"] = true, +} + + + + + + + + + + + + + + +local function platform_overrides(tbl) + + if not tbl then return end + + local tblp = tbl.platforms + + if type(tblp) == "table" then + for platform in cfg.each_platform() do + local platform_tbl = tblp[platform] + if type(platform_tbl) == "table" then + util.deep_merge(tbl, platform_tbl) + end + end + end + tbl.platforms = nil +end + +local function convert_dependencies(dependencies) + local qs = {} + for i = 1, #dependencies do + local parsed, err = queries.from_dep_string(dependencies[i]) + if not parsed then + return nil, "Parse error processing dependency '" .. dependencies[i] .. "': " .. tostring(err) + end + qs[i] = parsed + end + dependencies.queries = qs + return true +end + + + + + +local function configure_paths(rockspec) + local vars = {} + for k, v in pairs(cfg.variables) do + vars[k] = v + end + local name, version = rockspec.name, rockspec.version + vars.PREFIX = path.install_dir(name, version) + vars.LUADIR = path.lua_dir(name, version) + vars.LIBDIR = path.lib_dir(name, version) + vars.CONFDIR = path.conf_dir(name, version) + vars.BINDIR = path.bin_dir(name, version) + vars.DOCDIR = path.doc_dir(name, version) + rockspec.variables = vars +end + +function rockspecs.from_persisted_table(filename, rockspec, globals, quick) + + if rockspec.rockspec_format then + if vers.compare_versions(rockspec.rockspec_format, type_rockspec.rockspec_format) then + return nil, "Rockspec format " .. rockspec.rockspec_format .. " is not supported, please upgrade LuaRocks." + end + end + + if not quick then + local ok, err = type_rockspec.check(rockspec, globals or {}) + if not ok then + return nil, err + end + end + + + + + + do + local parsed_format = vers.parse_version(rockspec.rockspec_format or "1.0") + rockspec.format_is_at_least = function(self, version) + return parsed_format >= vers.parse_version(version) + end + end + + platform_overrides(rockspec.build) + platform_overrides(rockspec.dependencies) + platform_overrides(rockspec.build_dependencies) + platform_overrides(rockspec.test_dependencies) + platform_overrides(rockspec.external_dependencies) + platform_overrides(rockspec.source) + platform_overrides(rockspec.hooks) + platform_overrides(rockspec.test) + + rockspec.name = rockspec.package:lower() + + local protocol, pathname = dir.split_url(rockspec.source.url) + if dir.is_basic_protocol(protocol) then + rockspec.source.file = rockspec.source.file or dir.base_name(rockspec.source.url) + end + rockspec.source.protocol, rockspec.source.pathname = protocol, pathname + + + if rockspec.source.cvs_module then rockspec.source.module = rockspec.source.cvs_module end + if rockspec.source.cvs_tag then rockspec.source.tag = rockspec.source.cvs_tag end + + rockspec.local_abs_filename = filename + rockspec.source.dir_set = rockspec.source.dir ~= nil + rockspec.source.dir = rockspec.source.dir or rockspec.source.module + + rockspec.rocks_provided = util.get_rocks_provided(rockspec) + + rockspec.dependencies = rockspec.dependencies or {} + rockspec.build_dependencies = rockspec.build_dependencies or {} + rockspec.test_dependencies = rockspec.test_dependencies or {} + for _, d in ipairs({ rockspec.dependencies, rockspec.build_dependencies, rockspec.test_dependencies }) do + local _, err = convert_dependencies(d) + if err then + return nil, err + end + end + + if rockspec.build and + rockspec.build.type and + not vendored_build_type_set[rockspec.build.type] then + local build_pkg_name = "luarocks-build-" .. rockspec.build.type + if not rockspec.build_dependencies then + rockspec.build_dependencies = {} + end + + local found = false + for _, dep in ipairs(rockspec.build_dependencies.queries) do + if dep.name == build_pkg_name then + found = true + break + end + end + + if not found then + local query, errfromdep = queries.from_dep_string(build_pkg_name) + if errfromdep then + return nil, "Invalid dependency in rockspec: " .. errfromdep + end + table.insert(rockspec.build_dependencies.queries, query) + end + end + + if not quick then + configure_paths(rockspec) + end + + return rockspec +end + +return rockspecs diff --git a/src/luarocks/search.lua b/src/luarocks/search.lua new file mode 100644 index 00000000..eca17a8f --- /dev/null +++ b/src/luarocks/search.lua @@ -0,0 +1,385 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local assert = _tl_compat and _tl_compat.assert or assert; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local pairs = _tl_compat and _tl_compat.pairs or pairs; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table; local search = {} + +local dir = require("luarocks.dir") +local path = require("luarocks.path") +local manif = require("luarocks.manif") +local vers = require("luarocks.core.vers") +local cfg = require("luarocks.core.cfg") +local util = require("luarocks.util") +local queries = require("luarocks.queries") +local results = require("luarocks.results") + + + + + + + + + + + + +function search.store_result(result_tree, result) + + local name = result.name + local version = result.version + + if not result_tree[name] then result_tree[name] = {} end + if not result_tree[name][version] then result_tree[name][version] = {} end + table.insert(result_tree[name][version], { + arch = result.arch, + repo = result.repo, + namespace = result.namespace, + }) +end + + + + + + + + + + +local function store_if_match(result_tree, result, query) + + if result:satisfies(query) then + search.store_result(result_tree, result) + end +end + + + + + + + + + + +function search.disk_search(repo, query, result_tree) + + local fs = require("luarocks.fs") + + if not result_tree then + result_tree = {} + end + + 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 + local result = results.new(rname, rversion, repo, rarch) + store_if_match(result_tree, result, query) + elseif fs.is_dir(pathname) then + for version in fs.dir(pathname) do + if version:match("-%d+$") then + local namespace = path.read_namespace(name, version, repo) + local result = results.new(name, version, repo, "installed", namespace) + store_if_match(result_tree, result, query) + end + end + end + end + return result_tree +end + + + + + + + + + + + +local function manifest_search(result_tree, repo, query, lua_version, is_local) + + + if (not is_local) and query.namespace then + repo = repo .. "/manifests/" .. query.namespace + end + + local manifest, err, errcode = manif.load_manifest(repo, lua_version, not is_local) + if not manifest then + return nil, err, errcode + end + for name, versions in pairs(manifest.repository) do + for version, items in pairs(versions) do + local namespace = is_local and path.read_namespace(name, version, repo) or query.namespace + for _, item in ipairs(items) do + local result = results.new(name, version, repo, item.arch, namespace) + store_if_match(result_tree, result, query) + end + end + end + return true +end + +local function remote_manifest_search(result_tree, repo, query, lua_version) + return manifest_search(result_tree, repo, query, lua_version, false) +end + +function search.local_manifest_search(result_tree, repo, query, lua_version) + return manifest_search(result_tree, repo, query, lua_version, true) +end + + + + + + + +function search.search_repos(query, lua_version) + + local result_tree = {} + local repo = {} + for _, repostr in ipairs(cfg.rocks_servers) do + if type(repostr) == "string" then + repo = { repostr } + else + repo = repostr + end + for _, mirror in ipairs(repo) do + if not cfg.disabled_servers[mirror] then + local protocol, pathname = dir.split_url(mirror) + if protocol == "file" then + mirror = pathname + end + local ok, err, errcode = remote_manifest_search(result_tree, mirror, query, lua_version) + if errcode == "network" then + cfg.disabled_servers[mirror] = true + end + if ok then + break + else + util.warning("Failed searching manifest: " .. err) + if errcode == "downloader" then + break + end + end + end + end + end + + local provided_repo = "provided by VM or rocks_provided" + for name, version in pairs(util.get_rocks_provided()) do + local result = results.new(name, version, provided_repo, "installed") + store_if_match(result_tree, result, query) + end + return result_tree +end + + + + + + + +local function pick_latest_version(name, versions) + assert(not name:match("/")) + + local vtables = {} + for v, _ in pairs(versions) do + table.insert(vtables, vers.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 + + + + +local function supported_lua_versions(query) + local result_tree = {} + + for lua_version in util.lua_versions() do + if lua_version ~= cfg.lua_version then + util.printout("Checking for Lua " .. lua_version .. "...") + if search.search_repos(query, lua_version)[query.name] then + table.insert(result_tree, lua_version) + end + end + end + + return result_tree +end + + + + + + +function search.find_suitable_rock(query) + + local rocks_provided = util.get_rocks_provided() + + if rocks_provided[query.name] then + + return nil, "Rock " .. query.name .. " " .. rocks_provided[query.name] .. + " is already provided by VM or via 'rocks_provided' in the config file.", "provided" + end + + local result_tree = search.search_repos(query) + local first_rock = next(result_tree) + if not first_rock then + return nil, "No results matching query were found for Lua " .. cfg.lua_version .. ".", "notfound" + elseif next(result_tree, first_rock) then + + return nil, "Several rocks matched query.", "manyfound" + else + return pick_latest_version(query.name, result_tree[first_rock]) + end +end + +function search.find_rock_checking_lua_versions(query, check_lua_versions) + local url, err, errcode = search.find_suitable_rock(query) + if url then + return url + end + + if errcode == "notfound" then + local add + if check_lua_versions then + util.printout(query.name .. " not found for Lua " .. cfg.lua_version .. ".") + util.printout("Checking if available for other Lua versions...") + + + local lua_versions = supported_lua_versions(query) + + if #lua_versions ~= 0 then + + 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 + add = query.name .. " supports " .. versions_message + elseif #query.constraints == 1 and query.constraints[1].op == "==" then + local queryversion = tostring(query.constraints[1].version) + add = query.name .. " " .. queryversion .. " supports " .. versions_message + else + add = "Matching " .. query.name .. " versions support " .. versions_message + end + else + add = query.name .. " is not available for any Lua versions." + end + else + add = "To check if it is available for other Lua versions, use --check-lua-versions." + end + err = err .. "\n" .. add + end + + return nil, err +end + +function search.find_src_or_rockspec(name, namespace, version, check_lua_versions) + local query = queries.new(name, namespace, version, false, "src|rockspec") + local url, err = search.find_rock_checking_lua_versions(query, check_lua_versions) + if not url then + return nil, "Could not find a result named " .. tostring(query) .. ": " .. err + end + return url +end + + + + +function search.print_result_tree(result_tree, porcelain) + + if porcelain then + for packagestr, versions in util.sortedpairs(result_tree) do + for version, repos in util.sortedpairs(versions, vers.compare_versions) do + for _, repo in ipairs(repos) do + local nrepo = dir.normalize(repo.repo) + util.printout(packagestr, version, repo.arch, nrepo, repo.namespace) + end + end + end + return + end + + for packagestr, versions in util.sortedpairs(result_tree) do + local namespaces = {} + for version, repos in util.sortedpairs(versions, vers.compare_versions) do + for _, repo in ipairs(repos) do + local key = repo.namespace or "" + local list = namespaces[key] or {} + namespaces[key] = list + + repo.repo = dir.normalize(repo.repo) + table.insert(list, " " .. version .. " (" .. repo.arch .. ") - " .. path.root_dir(repo.repo)) + end + end + for key, list in util.sortedpairs(namespaces) do + util.printout(key == "" and packagestr or key .. "/" .. packagestr) + for _, line in ipairs(list) do + util.printout(line) + end + util.printout() + end + end +end + +function search.pick_installed_rock(query, given_tree) + + local result_tree = {} + 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.local_manifest_search(result_tree, rocks_dir, query) + end + if not next(result_tree) then + return nil, "cannot find package " .. tostring(query) .. "\nUse 'list' to find installed rocks." + end + + if not result_tree[query.name] and next(result_tree, next(result_tree)) then + local out = { "multiple installed packages match the name '" .. tostring(query) .. "':\n\n" } + for name, _ in util.sortedpairs(result_tree) do + table.insert(out, " " .. name .. "\n") + end + table.insert(out, "\nPlease specify a single rock.\n") + return nil, table.concat(out) + end + + local repo_url + + local name, versions + if result_tree[query.name] then + name, versions = query.name, result_tree[query.name] + else + name, versions = util.sortedpairs(result_tree)() + end + + local version, repositories = util.sortedpairs(versions, vers.compare_versions)() + for _, rp in ipairs(repositories) do repo_url = rp.repo end + + local repo = tree_map[repo_url] + return name, version, repo, repo_url +end + +return search diff --git a/src/luarocks/signing.lua b/src/luarocks/signing.lua new file mode 100644 index 00000000..cb91643a --- /dev/null +++ b/src/luarocks/signing.lua @@ -0,0 +1,48 @@ +local signing = {} + +local cfg = require("luarocks.core.cfg") +local fs = require("luarocks.fs") + +local function get_gpg() + local vars = cfg.variables + local gpg = vars.GPG + local gpg_ok, err = fs.is_tool_available(gpg, "gpg") + if not gpg_ok then + return nil, err + end + return gpg +end + +function signing.signature_url(url) + return url .. ".asc" +end + +function signing.sign_file(file) + local gpg, err = get_gpg() + if not gpg then + return nil, err + end + + local sigfile = file .. ".asc" + if fs.execute(gpg, "--armor", "--output", sigfile, "--detach-sign", file) then + return sigfile + else + return nil, "failed running " .. gpg .. " to sign " .. file + end +end + +function signing.verify_signature(file, sigfile) + local gpg, err = get_gpg() + if not gpg then + return nil, err + end + + if fs.execute(gpg, "--verify", sigfile, file) then + return true + else + return nil, "GPG returned a verification error" + end + +end + +return signing diff --git a/src/luarocks/test.lua b/src/luarocks/test.lua new file mode 100644 index 00000000..01dfae12 --- /dev/null +++ b/src/luarocks/test.lua @@ -0,0 +1,110 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local pcall = _tl_compat and _tl_compat.pcall or pcall; local table = _tl_compat and _tl_compat.table or table; local test = {} + + +local fetch = require("luarocks.fetch") +local deps = require("luarocks.deps") +local util = require("luarocks.util") + + + + + + + +local test_types = { + "busted", + "command", +} + +local test_modules = {} +local typetomod = {} +local modtotype = {} + +for _, test_type in ipairs(test_types) do + local mod + if test_type == "command" then + mod = require("luarocks.test.command") + elseif test_type == "busted" then + mod = require("luarocks.test.busted") + end + table.insert(test_modules, mod) + typetomod[test_type] = mod + modtotype[mod] = test_type +end + +local function get_test_type(rockspec) + if rockspec.test and rockspec.test.type then + return rockspec.test.type + end + + for _, test_module in ipairs(test_modules) do + if test_module.detect_type() then + return modtotype[test_module] + end + end + + return nil, "could not detect test type -- no test suite for " .. rockspec.package .. "?" +end + + +function test.run_test_suite(rockspec_arg, test_type, args, prepare) + local rockspec + if type(rockspec_arg) == "string" then + local err, errcode + rockspec, err, errcode = fetch.load_rockspec(rockspec_arg) + if err then + return nil, err, errcode + end + else + rockspec = rockspec_arg + end + + if not test_type then + local err + test_type, err = get_test_type(rockspec) + if not test_type then + return nil, err + end + end + + local all_deps = { + "dependencies", + "build_dependencies", + "test_dependencies", + } + for _, dep_kind in ipairs(all_deps) do + if (rockspec)[dep_kind] and next((rockspec)[dep_kind]) ~= nil then + local _, err, errcode = deps.fulfill_dependencies(rockspec, dep_kind, "all") + if err then + return nil, err, errcode + end + end + end + + local pok, test_mod = pcall(require, "luarocks.test." .. test_type) + if not pok then + return nil, "failed loading test execution module luarocks.test." .. test_type + end + + if prepare then + if test_type == "busted" then + return test_mod.run_tests(rockspec.test, { "--version" }) + else + return true + end + else + local flags = rockspec.test and rockspec.test.flags + if type(flags) == "table" then + util.variable_substitutions(flags, rockspec.variables) + + + for i = 1, #flags do + table.insert(args, i, flags[i]) + end + end + + return test_mod.run_tests(rockspec.test, args) + end +end + +return test diff --git a/src/luarocks/test/busted.lua b/src/luarocks/test/busted.lua new file mode 100644 index 00000000..bc00b33a --- /dev/null +++ b/src/luarocks/test/busted.lua @@ -0,0 +1,55 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local table = _tl_compat and _tl_compat.table or table; local _tl_table_unpack = unpack or table.unpack +local busted = {} + + +local fs = require("luarocks.fs") +local deps = require("luarocks.deps") +local path = require("luarocks.path") +local dir = require("luarocks.dir") +local queries = require("luarocks.queries") +local install = require("luarocks.cmd.install") + + + +function busted.detect_type() + if fs.exists(".busted") then + return true + end + return false +end + +function busted.run_tests(test, args) + if not test then + test = {} + end + + local ok, bustedver, where = deps.fulfill_dependency(queries.new("busted"), nil, nil, nil, "test_dependencies") + if not ok then + return nil, bustedver + end + + local busted_exe + if test.busted_executable then + busted_exe = test.busted_executable + else + busted_exe = dir.path(path.root_dir(where), "bin", "busted") + + + local busted_bat = dir.path(path.root_dir(where), "bin", "busted.bat") + + if not fs.exists(busted_exe) and not fs.exists(busted_bat) then + return nil, "'busted' executable failed to be installed" + end + end + + local err + ok, err = fs.execute(busted_exe, _tl_table_unpack(args)) + if ok then + return true + else + return nil, err or "test suite failed." + end +end + + +return busted diff --git a/src/luarocks/test/command.lua b/src/luarocks/test/command.lua new file mode 100644 index 00000000..41d30378 --- /dev/null +++ b/src/luarocks/test/command.lua @@ -0,0 +1,55 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local table = _tl_compat and _tl_compat.table or table; local _tl_table_unpack = unpack or table.unpack +local command = {} + + +local fs = require("luarocks.fs") +local cfg = require("luarocks.core.cfg") + + + +function command.detect_type() + if fs.exists("test.lua") then + return true + end + return false +end + +function command.run_tests(test, args) + if not test then + test = { + script = "test.lua", + } + end + + if not test.script and not test.command then + test.script = "test.lua" + end + + local ok + + if test.script then + local test_script = test.script + if not (type(test_script) == "string") then + return nil, "Malformed rockspec: 'script' expects a string" + end + if not fs.exists(test.script) then + return nil, "Test script " .. test.script .. " does not exist" + end + local lua = fs.Q(cfg.variables["LUA"]) + ok = fs.execute(lua, test.script, _tl_table_unpack(args)) + elseif test.command then + local test_command = test.command + if not (type(test_command) == "string") then + return nil, "Malformed rockspec: 'command' expects a string" + end + ok = fs.execute(test.command, _tl_table_unpack(args)) + end + + if ok then + return true + else + return nil, "tests failed with non-zero exit code" + end +end + +return command diff --git a/src/luarocks/tools/patch.lua b/src/luarocks/tools/patch.lua new file mode 100644 index 00000000..c9567bc6 --- /dev/null +++ b/src/luarocks/tools/patch.lua @@ -0,0 +1,746 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local assert = _tl_compat and _tl_compat.assert or assert; local debug = _tl_compat and _tl_compat.debug or debug; local io = _tl_compat and _tl_compat.io or io; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local math = _tl_compat and _tl_compat.math or math; local os = _tl_compat and _tl_compat.os or os; local pairs = _tl_compat and _tl_compat.pairs or pairs; local pcall = _tl_compat and _tl_compat.pcall or pcall; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table + + + + + + + + + +local patch = {Lineends = {}, Hunk = {}, File = {}, Files = {}, } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +local fs = require("luarocks.fs") + + + + + + + +local debugmode = false +local function debug(_) end +local function info(_) end +local function warning(s) io.stderr:write(s .. '\n') end + + +local function startswith(s, s2) + return s:sub(1, #s2) == s2 +end + + +local function endswith(s, s2) + return #s >= #s2 and s:sub(#s - #s2 + 1) == s2 +end + + +local function endlstrip(s) + return s:gsub('[\r\n]+$', '') +end + + +local function table_copy(t) + local t2 = {} + for k, v in pairs(t) do t2[k] = v end + return t2 +end + +local function exists(filename) + local fh = io.open(filename) + local result = fh ~= nil + if fh then fh:close() end + return result +end +local function isfile() return true end + +local function string_as_file(s) + return { + at = 0, + str = s, + len = #s, + eof = false, + read = function(self, n) + if self.eof then return nil end + local chunk = self.str:sub(self.at, self.at + n - 1) + self.at = self.at + n + if self.at > self.len then + self.eof = true + end + return chunk + end, + close = function(self) + self.eof = true + end, + } +end + + + + + + + + + + +local function file_lines(f) + local CHUNK_SIZE = 1024 + local buffer = "" + local pos_beg = 1 + return function() + local pos, chars + while 1 do + pos, chars = buffer:match('()([\r\n].)', pos_beg) + if pos or not f then + break + elseif f then + local chunk = f:read(CHUNK_SIZE) + if chunk then + buffer = buffer:sub(pos_beg) .. chunk + pos_beg = 1 + else + f = nil + end + end + end + local posi = math.tointeger(pos) + if not posi then + posi = #buffer + elseif chars == '\r\n' then + posi = posi + 1 + end + local line = buffer:sub(pos_beg, posi) + pos_beg = posi + 1 + if #line > 0 then + return line + end + end +end + +local function match_linerange(line) + local m1, m2, m3, m4 = line:match("^@@ %-(%d+),(%d+) %+(%d+),(%d+)") + if not m1 then m1, m3, m4 = line:match("^@@ %-(%d+) %+(%d+),(%d+)") end + if not m1 then m1, m2, m3 = line:match("^@@ %-(%d+),(%d+) %+(%d+)") end + if not m1 then m1, m3 = line:match("^@@ %-(%d+) %+(%d+)") end + return m1, m2, m3, m4 +end + +local function match_epoch(str) + return str:match("[^0-9]1969[^0-9]") or str:match("[^0-9]1970[^0-9]") +end + +function patch.read_patch(filename, data) + + local state = 'header' + + + + + + + local all_ok = true + local lineends = { lf = 0, crlf = 0, cr = 0 } + local files = { source = {}, target = {}, epoch = {}, hunks = {}, fileends = {}, hunkends = {} } + local nextfileno = 0 + local nexthunkno = 0 + + + + local hunkinfo = { + startsrc = nil, linessrc = nil, starttgt = nil, linestgt = nil, + invalid = false, text = {}, + } + local hunkactual = { linessrc = nil, linestgt = nil } + + info(string.format("reading patch %s", filename)) + + local fp + if data then + fp = string_as_file(data) + else + fp = filename == '-' and io.stdin or assert(io.open(filename, "rb")) + end + local lineno = 0 + + for line in file_lines(fp) do + lineno = lineno + 1 + if state == 'header' then + if startswith(line, "--- ") then + state = 'filenames' + end + + end + if state == 'hunkbody' then + + + if line:match("^[\r\n]*$") then + + line = " " .. line + end + + + if line:match("^[- +\\]") then + + local he = files.hunkends[nextfileno] + if endswith(line, "\r\n") then + he.crlf = he.crlf + 1 + elseif endswith(line, "\n") then + he.lf = he.lf + 1 + elseif endswith(line, "\r") then + he.cr = he.cr + 1 + end + if startswith(line, "-") then + hunkactual.linessrc = hunkactual.linessrc + 1 + elseif startswith(line, "+") then + hunkactual.linestgt = hunkactual.linestgt + 1 + elseif startswith(line, "\\") then + + else + hunkactual.linessrc = hunkactual.linessrc + 1 + hunkactual.linestgt = hunkactual.linestgt + 1 + end + table.insert(hunkinfo.text, line) + + else + warning(string.format("invalid hunk no.%d at %d for target file %s", + nexthunkno, lineno, files.target[nextfileno])) + + table.insert(files.hunks[nextfileno], table_copy(hunkinfo)) + files.hunks[nextfileno][nexthunkno].invalid = true + all_ok = false + state = 'hunkskip' + end + + + if hunkactual.linessrc > hunkinfo.linessrc or + hunkactual.linestgt > hunkinfo.linestgt then + + warning(string.format("extra hunk no.%d lines at %d for target %s", + nexthunkno, lineno, files.target[nextfileno])) + + table.insert(files.hunks[nextfileno], table_copy(hunkinfo)) + files.hunks[nextfileno][nexthunkno].invalid = true + state = 'hunkskip' + elseif hunkinfo.linessrc == hunkactual.linessrc and + hunkinfo.linestgt == hunkactual.linestgt then + + table.insert(files.hunks[nextfileno], table_copy(hunkinfo)) + state = 'hunkskip' + + + local ends = files.hunkends[nextfileno] + if (ends.cr ~= 0 and 1 or 0) + (ends.crlf ~= 0 and 1 or 0) + + (ends.lf ~= 0 and 1 or 0) > 1 then + + warning(string.format("inconsistent line ends in patch hunks for %s", + files.source[nextfileno])) + end + end + + end + + if state == 'hunkskip' then + if match_linerange(line) then + state = 'hunkhead' + elseif startswith(line, "--- ") then + state = 'filenames' + if debugmode and #files.source > 0 then + debug(string.format("- %2d hunks for %s", #files.hunks[nextfileno], + files.source[nextfileno])) + end + end + + end + local advance + if state == 'filenames' then + if startswith(line, "--- ") then + if files.source[nextfileno] then + all_ok = false + warning(string.format("skipping invalid patch for %s", + files.source[nextfileno + 1])) + table.remove(files.source, nextfileno + 1) + + + end + + + + local match, rest = line:match("^%-%-%- ([^ \t\r\n]+)(.*)") + if not match then + all_ok = false + warning(string.format("skipping invalid filename at line %d", lineno + 1)) + state = 'header' + else + if match_epoch(rest) then + files.epoch[nextfileno + 1] = true + end + table.insert(files.source, match) + end + elseif not startswith(line, "+++ ") then + if files.source[nextfileno] then + all_ok = false + warning(string.format("skipping invalid patch with no target for %s", + files.source[nextfileno + 1])) + table.remove(files.source, nextfileno + 1) + else + + warning("skipping invalid target patch") + end + state = 'header' + else + if files.target[nextfileno] then + all_ok = false + warning(string.format("skipping invalid patch - double target at line %d", + lineno + 1)) + table.remove(files.source, nextfileno + 1) + table.remove(files.target, nextfileno + 1) + nextfileno = nextfileno - 1 + + + state = 'header' + else + + + + local re_filename = "^%+%+%+ ([^ \t\r\n]+)(.*)$" + local match, rest = line:match(re_filename) + if not match then + all_ok = false + warning(string.format( + "skipping invalid patch - no target filename at line %d", + lineno + 1)) + state = 'header' + else + table.insert(files.target, match) + nextfileno = nextfileno + 1 + if match_epoch(rest) then + files.epoch[nextfileno] = true + end + nexthunkno = 0 + table.insert(files.hunks, {}) + table.insert(files.hunkends, table_copy(lineends)) + table.insert(files.fileends, table_copy(lineends)) + state = 'hunkhead' + advance = true + end + end + end + + end + if not advance and state == 'hunkhead' then + local m1, m2, m3, m4 = match_linerange(line) + if not m1 then + if not files.hunks[nextfileno - 1] then + all_ok = false + warning(string.format("skipping invalid patch with no hunks for file %s", + files.target[nextfileno])) + end + state = 'header' + else + hunkinfo.startsrc = math.tointeger(m1) + hunkinfo.linessrc = math.tointeger(m2) or 1 + hunkinfo.starttgt = math.tointeger(m3) + hunkinfo.linestgt = math.tointeger(m4) or 1 + hunkinfo.invalid = false + hunkinfo.text = {} + + hunkactual.linessrc = 0 + hunkactual.linestgt = 0 + + state = 'hunkbody' + nexthunkno = nexthunkno + 1 + end + + end + end + if state ~= 'hunkskip' then + warning(string.format("patch file incomplete - %s", filename)) + all_ok = false + + else + + if debugmode and #files.source > 0 then + debug(string.format("- %2d hunks for %s", #files.hunks[nextfileno], + files.source[nextfileno])) + end + end + + local sum = 0; for _, hset in ipairs(files.hunks) do sum = sum + #hset end + info(string.format("total files: %d total hunks: %d", #files.source, sum)) + fp:close() + return files, all_ok +end + +local function find_hunk(file, h, hno) + for fuzz = 0, 2 do + local lineno = h.startsrc + for i = 0, #file do + local found = true + local location = lineno + for l, hline in ipairs(h.text) do + if l > fuzz then + + if startswith(hline, " ") or startswith(hline, "-") then + local line = file[lineno] + lineno = lineno + 1 + if not line or #line == 0 then + found = false + break + end + if endlstrip(line) ~= endlstrip(hline:sub(2)) then + found = false + break + end + end + end + end + if found then + local offset = location - h.startsrc - fuzz + if offset ~= 0 then + warning(string.format("Hunk %d found at offset %d%s...", hno, offset, fuzz == 0 and "" or string.format(" (fuzz %d)", fuzz))) + end + h.startsrc = location + h.starttgt = h.starttgt + offset + for _ = 1, fuzz do + table.remove(h.text, 1) + table.remove(h.text, #h.text) + end + return true + end + lineno = i + end + end + return false +end + +local function load_file(filename) + local fp = assert(io.open(filename)) + local file = {} + local readline = file_lines(fp) + while true do + local line = readline() + if not line then break end + table.insert(file, line) + end + fp:close() + return file +end + +local function find_hunks(file, hunks) + for hno, h in ipairs(hunks) do + find_hunk(file, h, hno) + end +end + +local function check_patched(file, hunks) + local lineno = 1 + local _, err = pcall(function() + if #file == 0 then + error('nomatch', 0) + end + for hno, h in ipairs(hunks) do + + if #file < h.starttgt then + error('nomatch', 0) + end + lineno = h.starttgt + for _, hline in ipairs(h.text) do + + if not startswith(hline, "-") and not startswith(hline, "\\") then + local line = file[lineno] + lineno = lineno + 1 + if #line == 0 then + error('nomatch', 0) + end + if endlstrip(line) ~= endlstrip(hline:sub(2)) then + warning(string.format("file is not patched - failed hunk: %d", hno)) + error('nomatch', 0) + end + end + end + end + end) + + return err ~= 'nomatch' +end + +local function patch_hunks(srcname, tgtname, hunks) + local src = assert(io.open(srcname, "rb")) + local tgt = assert(io.open(tgtname, "wb")) + + local src_readline = file_lines(src) + + + + + + + + local srclineno = 1 + local lineends = { ['\n'] = 0, ['\r\n'] = 0, ['\r'] = 0 } + for hno, h in ipairs(hunks) do + debug(string.format("processing hunk %d for file %s", hno, tgtname)) + + while srclineno < h.startsrc do + local line = src_readline() + + if endswith(line, "\r\n") then + lineends["\r\n"] = lineends["\r\n"] + 1 + elseif endswith(line, "\n") then + lineends["\n"] = lineends["\n"] + 1 + elseif endswith(line, "\r") then + lineends["\r"] = lineends["\r"] + 1 + end + tgt:write(line) + srclineno = srclineno + 1 + end + + for _, hline in ipairs(h.text) do + + if startswith(hline, "-") or startswith(hline, "\\") then + src_readline() + srclineno = srclineno + 1 + else + if not startswith(hline, "+") then + src_readline() + srclineno = srclineno + 1 + end + local line2write = hline:sub(2) + + local sum = 0 + for _, v in pairs(lineends) do if v > 0 then sum = sum + 1 end end + if sum == 1 then + local newline + for k, v in pairs(lineends) do if v ~= 0 then newline = k end end + tgt:write(endlstrip(line2write) .. newline) + else + tgt:write(line2write) + end + end + end + end + for line in src_readline do + tgt:write(line) + end + tgt:close() + src:close() + return true +end + +local function strip_dirs(filename, strip) + if strip == nil then return filename end + for _ = 1, strip do + filename = filename:gsub("^[^/]*/", "") + end + return filename +end + +local function write_new_file(filename, hunk) + local fh = io.open(filename, "wb") + if not fh then return false end + for _, hline in ipairs(hunk.text) do + local c = hline:sub(1, 1) + if c ~= "+" and c ~= "-" and c ~= " " then + return false, "malformed patch" + end + fh:write(hline:sub(2)) + end + fh:close() + return true +end + +local function patch_file(source, target, epoch, hunks, strip, create_delete) + local create_file = false + if create_delete then + local is_src_epoch = epoch and #hunks == 1 and hunks[1].startsrc == 0 and hunks[1].linessrc == 0 + if is_src_epoch or source == "/dev/null" then + info(string.format("will create %s", target)) + create_file = true + end + end + if create_file then + return write_new_file(fs.absolute_name(strip_dirs(target, strip)), hunks[1]) + end + source = strip_dirs(source, strip) + local f2patch = source + if not exists(f2patch) then + f2patch = strip_dirs(target, strip) + f2patch = fs.absolute_name(f2patch) + if not exists(f2patch) then + warning(string.format("source/target file does not exist\n--- %s\n+++ %s", + source, f2patch)) + return false + end + end + + if not isfile() then + warning(string.format("not a file - %s", f2patch)) + return false + end + + source = f2patch + + + local file = load_file(source) + local hunkno = 1 + local hunk = hunks[hunkno] + local hunkfind = {} + local validhunks = 0 + local canpatch = false + local hunklineno + if not file then + return nil, "failed reading file " .. source + end + + if create_delete then + if epoch and #hunks == 1 and hunks[1].starttgt == 0 and hunks[1].linestgt == 0 then + local ok = os.remove(source) + if not ok then + return false + end + info(string.format("successfully removed %s", source)) + return true + end + end + + find_hunks(file, hunks) + + local function process_line(line, lineno) + if not hunk or lineno < hunk.startsrc then + return false + end + if lineno == hunk.startsrc then + hunkfind = {} + for _, x in ipairs(hunk.text) do + if x:sub(1, 1) == ' ' or x:sub(1, 1) == '-' then + hunkfind[#hunkfind + 1] = endlstrip(x:sub(2)) + end + end + hunklineno = 1 + + + end + + if lineno < hunk.startsrc + #hunkfind - 1 then + if endlstrip(line) == hunkfind[hunklineno] then + hunklineno = hunklineno + 1 + else + debug(string.format("hunk no.%d doesn't match source file %s", + hunkno, source)) + + hunkno = hunkno + 1 + if hunkno <= #hunks then + hunk = hunks[hunkno] + return false + else + return true + end + end + end + + if lineno == hunk.startsrc + #hunkfind - 1 then + debug(string.format("file %s hunk no.%d -- is ready to be patched", + source, hunkno)) + hunkno = hunkno + 1 + validhunks = validhunks + 1 + if hunkno <= #hunks then + hunk = hunks[hunkno] + else + if validhunks == #hunks then + + canpatch = true + return true + end + end + end + return false + end + + local done = false + for lineno, line in ipairs(file) do + done = process_line(line, lineno) + if done then + break + end + end + if not done then + if hunkno <= #hunks and not create_file then + warning(string.format("premature end of source file %s at hunk %d", + source, hunkno)) + return false + end + end + if validhunks < #hunks then + if check_patched(file, hunks) then + warning(string.format("already patched %s", source)) + elseif not create_file then + warning(string.format("source file is different - %s", source)) + return false + end + end + if not canpatch then + return true + end + local backupname = source .. ".orig" + if exists(backupname) then + warning(string.format("can't backup original file to %s - aborting", + backupname)) + return false + end + local ok = os.rename(source, backupname) + if not ok then + warning(string.format("failed backing up %s when patching", source)) + return false + end + patch_hunks(backupname, source, hunks) + info(string.format("successfully patched %s", source)) + os.remove(backupname) + return true +end + +function patch.apply_patch(the_patch, strip, create_delete) + local all_ok = true + local total = #the_patch.source + for fileno, source in ipairs(the_patch.source) do + local target = the_patch.target[fileno] + local hunks = the_patch.hunks[fileno] + local epoch = the_patch.epoch[fileno] + info(string.format("processing %d/%d:\t %s", fileno, total, source)) + local ok = patch_file(source, target, epoch, hunks, strip, create_delete) + all_ok = all_ok and ok + end + + return all_ok +end + +return patch diff --git a/src/luarocks/tools/tar.lua b/src/luarocks/tools/tar.lua new file mode 100644 index 00000000..25e1d0ec --- /dev/null +++ b/src/luarocks/tools/tar.lua @@ -0,0 +1,210 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local io = _tl_compat and _tl_compat.io or io; local math = _tl_compat and _tl_compat.math or math; local string = _tl_compat and _tl_compat.string or string + +local tar = {Header = {}, } + + + + + + + + + + + + + + + + + + + + +local fs = require("luarocks.fs") +local dir = require("luarocks.dir") +local fun = require("luarocks.fun") + + + +local blocksize = 512 + +local function get_typeflag(flag) + if flag == "0" or flag == "\0" then return "file" + elseif flag == "1" then return "link" + elseif flag == "2" then return "symlink" + elseif flag == "3" then return "character" + elseif flag == "4" then return "block" + elseif flag == "5" then return "directory" + elseif flag == "6" then return "fifo" + elseif flag == "7" then return "contiguous" + elseif flag == "x" then return "next file" + elseif flag == "g" then return "global extended header" + elseif flag == "L" then return "long name" + elseif flag == "K" then return "long link name" + end + return "unknown" +end + +local function octal_to_number(octal) + local exp = 0 + local number = 0 + octal = octal:gsub("%s", "") + for i = #octal, 1, -1 do + local digit = math.tointeger(octal:sub(i, i)) + if not digit then + break + end + number = number + (digit * math.tointeger(8 ^ exp)) + exp = exp + 1 + end + return number +end + +local function checksum_header(block) + local sum = 256 + + if block:byte(1) == 0 then + return 0 + end + + for i = 1, 148 do + local b = block:byte(i) or 0 + sum = sum + b + end + for i = 157, 500 do + local b = block:byte(i) or 0 + sum = sum + b + end + + return sum +end + +local function nullterm(s) + return s:match("^[^%z]*") +end + +local function read_header_block(block) + local header = {} + header.name = nullterm(block:sub(1, 100)) + header.mode = nullterm(block:sub(101, 108)):gsub(" ", "") + header.uid = octal_to_number(nullterm(block:sub(109, 116))) + header.gid = octal_to_number(nullterm(block:sub(117, 124))) + header.size = octal_to_number(nullterm(block:sub(125, 136))) + header.mtime = octal_to_number(nullterm(block:sub(137, 148))) + header.chksum = octal_to_number(nullterm(block:sub(149, 156))) + header.typeflag = get_typeflag(block:sub(157, 157)) + header.linkname = nullterm(block:sub(158, 257)) + header.magic = block:sub(258, 263) + header.version = block:sub(264, 265) + header.uname = nullterm(block:sub(266, 297)) + header.gname = nullterm(block:sub(298, 329)) + header.devmajor = octal_to_number(nullterm(block:sub(330, 337))) + header.devminor = octal_to_number(nullterm(block:sub(338, 345))) + header.prefix = block:sub(346, 500) + + + + + + + + if header.typeflag == "unknown" then + if checksum_header(block) ~= header.chksum then + return false, "Failed header checksum" + end + end + return header +end + +function tar.untar(filename, destdir) + + local tar_handle = io.open(filename, "rb") + if not tar_handle then return nil, "Error opening file " .. filename end + + local long_name, long_link_name + local ok, err + local make_dir = fun.memoize(fs.make_dir) + while true do + local block + repeat + block = tar_handle:read(blocksize) + until (not block) or block:byte(1) > 0 + if not block then break end + if #block < blocksize then + ok, err = nil, "Invalid block size -- corrupted file?" + break + end + + local headerp + headerp, err = read_header_block(block) + if not headerp then + ok = false + break + end + local header = headerp + local file_data = "" + if header.size > 0 then + local nread = math.ceil(header.size / blocksize) * blocksize + file_data = tar_handle:read(header.size) + if nread > header.size then + tar_handle:seek("cur", nread - header.size) + end + end + + if header.typeflag == "long name" then + long_name = nullterm(file_data) + elseif header.typeflag == "long link name" then + long_link_name = nullterm(file_data) + else + if long_name then + header.name = long_name + long_name = nil + end + if long_link_name then + header.name = long_link_name + long_link_name = nil + end + end + local pathname = dir.path(destdir, header.name) + pathname = fs.absolute_name(pathname) + if header.typeflag == "directory" then + ok, err = make_dir(pathname) + if not ok then + break + end + elseif header.typeflag == "file" then + local dirname = dir.dir_name(pathname) + if dirname ~= "" then + ok, err = make_dir(dirname) + if not ok then + break + end + end + local file_handle + file_handle, err = io.open(pathname, "wb") + if not file_handle then + ok = nil + break + end + file_handle:write(file_data) + file_handle:close() + fs.set_time(pathname, header.mtime) + if header.mode:match("[75]") then + fs.set_permissions(pathname, "exec", "all") + else + fs.set_permissions(pathname, "read", "all") + end + end + + + + + + + end + tar_handle:close() + return ok, err +end + +return tar diff --git a/src/luarocks/tools/zip.lua b/src/luarocks/tools/zip.lua new file mode 100644 index 00000000..b54d9e86 --- /dev/null +++ b/src/luarocks/tools/zip.lua @@ -0,0 +1,575 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local io = _tl_compat and _tl_compat.io or io; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local math = _tl_compat and _tl_compat.math or math; local os = _tl_compat and _tl_compat.os or os; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table; local _tl_table_pack = table.pack or function(...) return { n = select("#", ...), ... } end + + +local zip = {ZipHandle = {}, LocalFileHeader = {}, Zip = {}, } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +local zlib = require("zlib") +local fs = require("luarocks.fs") +local fun = require("luarocks.fun") +local dir = require("luarocks.dir") + + + + + +local function shr(n, m) + return math.floor(n / 2 ^ m) +end + +local function shl(n, m) + return (n * 2 ^ m) +end + +local function lowbits(n, m) + return (n % 2 ^ m) +end + +local function mode_to_windowbits(mode) + if mode == "gzip" then + return 31 + elseif mode == "zlib" then + return 0 + elseif mode == "raw" then + return -15 + end +end + + + +local zlib_compress +local zlib_uncompress +local zlib_crc32 +if zlib._VERSION:match("^lua%-zlib") then + function zlib_compress(data, mode) + return (zlib.deflate(6, mode_to_windowbits(mode))(data, "finish")) + end + + function zlib_uncompress(data, mode) + return (zlib.inflate(mode_to_windowbits(mode))(data)) + end + + function zlib_crc32(data) + return zlib.crc32()(data) + end +elseif zlib._VERSION:match("^lzlib") then + function zlib_compress(data, mode) + return zlib.compress(data, -1, nil, mode_to_windowbits(mode)) + end + + function zlib_uncompress(data, mode) + return zlib.decompress(data, mode_to_windowbits(mode)) + end + + function zlib_crc32(data) + return zlib.crc32(zlib.crc32(), data) + end +else + error("unknown zlib library", 0) +end + +local function number_to_lestring(number, nbytes) + local out = {} + for _ = 1, nbytes do + local byte = number % 256 + table.insert(out, string.char(byte)) + number = (number - byte) / 256 + end + return table.concat(out) +end + +local function lestring_to_number(str) + local n = 0 + local bytes = { string.byte(str, 1, #str) } + for b = 1, #str do + n = n + shl(bytes[b], (b - 1) * 8) + end + return math.floor(n) +end + +local LOCAL_FILE_HEADER_SIGNATURE = number_to_lestring(0x04034b50, 4) +local DATA_DESCRIPTOR_SIGNATURE = number_to_lestring(0x08074b50, 4) +local CENTRAL_DIRECTORY_SIGNATURE = number_to_lestring(0x02014b50, 4) +local END_OF_CENTRAL_DIR_SIGNATURE = number_to_lestring(0x06054b50, 4) + + + + + +local function zipwriter_open_new_file_in_zip(self, filename) + if self.in_open_file then + self:close_file_in_zip() + return nil + end + local lfh = {} + self.local_file_header = lfh + lfh.last_mod_file_time = 0 + lfh.last_mod_file_date = 0 + lfh.file_name_length = #filename + lfh.extra_field_length = 0 + lfh.file_name = filename:gsub("\\", "/") + lfh.external_attr = shl(493, 16) + self.in_open_file = true + return true +end + + + + + +local function zipwriter_write_file_in_zip(self, data) + if not self.in_open_file then + return nil + end + local lfh = self.local_file_header + local compressed = zlib_compress(data, "raw") + lfh.crc32 = zlib_crc32(data) + lfh.compressed_size = #compressed + lfh.uncompressed_size = #data + self.data = compressed + return true +end + + + + +local function zipwriter_close_file_in_zip(self) + local zh = self.ZipHandle + + if not self.in_open_file then + return nil + end + + + local lfh = self.local_file_header + lfh.offset = zh:seek() + zh:write(LOCAL_FILE_HEADER_SIGNATURE) + zh:write(number_to_lestring(20, 2)) + zh:write(number_to_lestring(4, 2)) + zh:write(number_to_lestring(8, 2)) + zh:write(number_to_lestring(lfh.last_mod_file_time, 2)) + zh:write(number_to_lestring(lfh.last_mod_file_date, 2)) + zh:write(number_to_lestring(lfh.crc32, 4)) + zh:write(number_to_lestring(lfh.compressed_size, 4)) + zh:write(number_to_lestring(lfh.uncompressed_size, 4)) + zh:write(number_to_lestring(lfh.file_name_length, 2)) + zh:write(number_to_lestring(lfh.extra_field_length, 2)) + zh:write(lfh.file_name) + + + zh:write(self.data) + + + zh:write(DATA_DESCRIPTOR_SIGNATURE) + zh:write(number_to_lestring(lfh.crc32, 4)) + zh:write(number_to_lestring(lfh.compressed_size, 4)) + zh:write(number_to_lestring(lfh.uncompressed_size, 4)) + + table.insert(self.files, lfh) + self.in_open_file = false + + return true +end + + + +local function zipwriter_add(self, file) + local fin + local ok, err = self:open_new_file_in_zip(file) + if not ok then + err = "error in opening " .. file .. " in zipfile" + else + fin = io.open(fs.absolute_name(file), "rb") + if not fin then + ok = false + err = "error opening " .. file .. " for reading" + end + end + if ok then + local data = fin:read("*a") + if not data then + err = "error reading " .. file + ok = false + else + ok = self:write_file_in_zip(data) + if not ok then + err = "error in writing " .. file .. " in the zipfile" + end + end + end + if fin then + fin:close() + end + if ok then + ok = self:close_file_in_zip() + if not ok then + err = "error in writing " .. file .. " in the zipfile" + end + end + return ok == true, err +end + + + + +local function zipwriter_close(self) + local zh = self.ZipHandle + + local central_directory_offset = zh:seek() + + local size_of_central_directory = 0 + + for _, lfh in ipairs(self.files) do + zh:write(CENTRAL_DIRECTORY_SIGNATURE) + zh:write(number_to_lestring(3, 2)) + zh:write(number_to_lestring(20, 2)) + zh:write(number_to_lestring(0, 2)) + zh:write(number_to_lestring(8, 2)) + zh:write(number_to_lestring(lfh.last_mod_file_time, 2)) + zh:write(number_to_lestring(lfh.last_mod_file_date, 2)) + zh:write(number_to_lestring(lfh.crc32, 4)) + zh:write(number_to_lestring(lfh.compressed_size, 4)) + zh:write(number_to_lestring(lfh.uncompressed_size, 4)) + zh:write(number_to_lestring(lfh.file_name_length, 2)) + zh:write(number_to_lestring(lfh.extra_field_length, 2)) + zh:write(number_to_lestring(0, 2)) + zh:write(number_to_lestring(0, 2)) + zh:write(number_to_lestring(0, 2)) + zh:write(number_to_lestring(lfh.external_attr, 4)) + zh:write(number_to_lestring(lfh.offset, 4)) + zh:write(lfh.file_name) + size_of_central_directory = size_of_central_directory + 46 + lfh.file_name_length + end + + + zh:write(END_OF_CENTRAL_DIR_SIGNATURE) + zh:write(number_to_lestring(0, 2)) + zh:write(number_to_lestring(0, 2)) + zh:write(number_to_lestring(#self.files, 2)) + zh:write(number_to_lestring(#self.files, 2)) + zh:write(number_to_lestring(size_of_central_directory, 4)) + zh:write(number_to_lestring(central_directory_offset, 4)) + zh:write(number_to_lestring(0, 2)) + zh:close() + + return true +end + + + + +function zip.new_zipwriter(name) + + local zw = {} + + zw.ZipHandle = io.open(fs.absolute_name(name), "wb") + if not zw.ZipHandle then + return nil + end + zw.files = {} + zw.in_open_file = false + + zw.add = zipwriter_add + zw.close = zipwriter_close + zw.open_new_file_in_zip = zipwriter_open_new_file_in_zip + zw.write_file_in_zip = zipwriter_write_file_in_zip + zw.close_file_in_zip = zipwriter_close_file_in_zip + + return zw +end + + + + + + + +function zip.zip(zipfile, ...) + local zw = zip.new_zipwriter(zipfile) + if not zw then + return nil, "error opening " .. zipfile + end + + local args = _tl_table_pack(...) + local ok, err + for i = 1, args.n do + local file = args[i] + if fs.is_dir(file) then + for _, entry in ipairs(fs.find(file)) do + local fullname = dir.path(file, entry) + if fs.is_file(fullname) then + ok, err = zw:add(fullname) + if not ok then break end + end + end + else + ok, err = zw:add(file) + if not ok then break end + end + end + + zw:close() + return ok, err +end + + +local function ziptime_to_luatime(ztime, zdate) + local date = { + year = shr(zdate, 9) + 1980, + month = shr(lowbits(zdate, 9), 5), + day = lowbits(zdate, 5), + hour = shr(ztime, 11), + min = shr(lowbits(ztime, 11), 5), + sec = lowbits(ztime, 5) * 2, + } + + if date.month == 0 then date.month = 1 end + if date.day == 0 then date.day = 1 end + + return date +end + +local function read_file_in_zip(zh, cdr) + local sig = zh:read(4) + if sig ~= LOCAL_FILE_HEADER_SIGNATURE then + return nil, "failed reading Local File Header signature" + end + + + + zh:seek("cur", 22) + local file_name_length = lestring_to_number(zh:read(2)) + local extra_field_length = lestring_to_number(zh:read(2)) + zh:read(file_name_length) + zh:read(extra_field_length) + + local data = zh:read(cdr.compressed_size) + + local uncompressed + if cdr.compression_method == 8 then + uncompressed = zlib_uncompress(data, "raw") + elseif cdr.compression_method == 0 then + uncompressed = data + else + return nil, "unknown compression method " .. cdr.compression_method + end + + if #uncompressed ~= cdr.uncompressed_size then + return nil, "uncompressed size doesn't match" + end + if cdr.crc32 ~= zlib_crc32(uncompressed) then + return nil, "crc32 failed (expected " .. cdr.crc32 .. ") - data: " .. uncompressed + end + + return uncompressed +end + +local function process_end_of_central_dir(zh) + local at, errend = zh:seek("end", -22) + if not at then + return nil, errend + end + + while true do + local sig = zh:read(4) + if sig == END_OF_CENTRAL_DIR_SIGNATURE then + break + end + at = at - 1 + local at1 = zh:seek("set", at) + if at1 ~= at then + return nil, "Could not find End of Central Directory signature" + end + end + + + + + + zh:seek("cur", 6) + + local central_directory_entries = lestring_to_number(zh:read(2)) + + + zh:seek("cur", 4) + + local central_directory_offset = lestring_to_number(zh:read(4)) + + return central_directory_entries, central_directory_offset +end + +local function process_central_dir(zh, cd_entries) + + local files = {} + + for i = 1, cd_entries do + local sig = zh:read(4) + if sig ~= CENTRAL_DIRECTORY_SIGNATURE then + return nil, "failed reading Central Directory signature" + end + + local cdr = {} + files[i] = cdr + + cdr.version_made_by = lestring_to_number(zh:read(2)) + cdr.version_needed = lestring_to_number(zh:read(2)) + cdr.bitflag = lestring_to_number(zh:read(2)) + cdr.compression_method = lestring_to_number(zh:read(2)) + cdr.last_mod_file_time = lestring_to_number(zh:read(2)) + cdr.last_mod_file_date = lestring_to_number(zh:read(2)) + cdr.last_mod_luatime = ziptime_to_luatime(cdr.last_mod_file_time, cdr.last_mod_file_date) + cdr.crc32 = lestring_to_number(zh:read(4)) + cdr.compressed_size = lestring_to_number(zh:read(4)) + cdr.uncompressed_size = lestring_to_number(zh:read(4)) + cdr.file_name_length = lestring_to_number(zh:read(2)) + cdr.extra_field_length = lestring_to_number(zh:read(2)) + cdr.file_comment_length = lestring_to_number(zh:read(2)) + cdr.disk_number_start = lestring_to_number(zh:read(2)) + cdr.internal_attr = lestring_to_number(zh:read(2)) + cdr.external_attr = lestring_to_number(zh:read(4)) + cdr.offset = lestring_to_number(zh:read(4)) + cdr.file_name = zh:read(cdr.file_name_length) + cdr.extra_field = zh:read(cdr.extra_field_length) + cdr.file_comment = zh:read(cdr.file_comment_length) + end + return files +end + + + + + +function zip.unzip(zipfile) + zipfile = fs.absolute_name(zipfile) + local zh, erropen = io.open(zipfile, "rb") + if not zh then + return nil, erropen + end + + local cd_entries, cd_offset = process_end_of_central_dir(zh) + if type(cd_offset) == "string" then + return nil, cd_offset + end + + local okseek, errseek = zh:seek("set", cd_offset) + if not okseek then + return nil, errseek + end + + local files, errproc = process_central_dir(zh, cd_entries) + if not files then + return nil, errproc + end + + for _, cdr in ipairs(files) do + local file = cdr.file_name + if file:sub(#file) == "/" then + local okmake, errmake = fs.make_dir(dir.path(fs.current_dir(), file)) + if not okmake then + return nil, errmake + end + else + local base = dir.dir_name(file) + if base ~= "" then + base = dir.path(fs.current_dir(), base) + if not fs.is_dir(base) then + local okmake, errmake = fs.make_dir(base) + if not okmake then + return nil, errmake + end + end + end + + local okseek2, errseek2 = zh:seek("set", cdr.offset) + if not okseek2 then + return nil, errseek2 + end + + local contents, err = read_file_in_zip(zh, cdr) + if not contents then + return nil, err + end + local pathname = dir.path(fs.current_dir(), file) + local wf, erropen2 = io.open(pathname, "wb") + if not wf then + zh:close() + return nil, erropen2 + end + wf:write(contents) + wf:close() + + if cdr.external_attr > 0 then + fs.set_permissions(pathname, "exec", "all") + else + fs.set_permissions(pathname, "read", "all") + end + fs.set_time(pathname, cdr.last_mod_luatime) + end + end + zh:close() + return true +end + +function zip.gzip(input_filename, output_filename) + + if not output_filename then + output_filename = input_filename .. ".gz" + end + + local fn = fun.partial(fun.flip(zlib_compress), "gzip") + return fs.filter_file(fn, input_filename, output_filename) +end + +function zip.gunzip(input_filename, output_filename) + + if not output_filename then + output_filename = input_filename:gsub("%.gz$", "") + end + + local fn = fun.partial(fun.flip(zlib_uncompress), "gzip") + return fs.filter_file(fn, input_filename, output_filename) +end + +return zip diff --git a/src/luarocks/type/manifest.lua b/src/luarocks/type/manifest.lua new file mode 100644 index 00000000..3f3be728 --- /dev/null +++ b/src/luarocks/type/manifest.lua @@ -0,0 +1,92 @@ +local type_manifest = {} + + + + +local type_check = require("luarocks.type_check") + +local manifest_formats = type_check.declare_schemas({ + ["3.0"] = { + fields = { + repository = { + _mandatory = true, + + _any = { + + _any = { + + _any = { + fields = { + arch = { _type = "string", _mandatory = true }, + modules = { _any = { _type = "string" } }, + commands = { _any = { _type = "string" } }, + dependencies = { _any = { _type = "string" } }, + + }, + }, + }, + }, + }, + modules = { + _mandatory = true, + + _any = { + + _any = { _type = "string" }, + }, + }, + commands = { + _mandatory = true, + + _any = { + + _any = { _type = "string" }, + }, + }, + dependencies = { + + _any = { + + _any = { + + _any = { + fields = { + name = { _type = "string" }, + namespace = { _type = "string" }, + constraints = { + _any = { + fields = { + no_upgrade = { _type = "boolean" }, + op = { _type = "string" }, + version = { + fields = { + string = { _type = "string" }, + }, + _any = { _type = "number" }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, +}) + + + + + + + +function type_manifest.check(manifest, globals) + local format = manifest_formats["3.0"] + local ok, err = type_check.check_undeclared_globals(globals, format) + if not ok then return nil, err end + return type_check.type_check_table("3.0", manifest, format, "") +end + +return type_manifest diff --git a/src/luarocks/type/rockspec.lua b/src/luarocks/type/rockspec.lua new file mode 100644 index 00000000..cd4044f6 --- /dev/null +++ b/src/luarocks/type/rockspec.lua @@ -0,0 +1,261 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local type_rockspec = {} + + + + + + + + +local type_check = require("luarocks.type_check") + + + +type_rockspec.rockspec_format = "3.0" + + + + + + + + + + + + + + +local rockspec_formats, versions = type_check.declare_schemas({ + ["1.0"] = { + fields = { + rockspec_format = { _type = "string" }, + package = { _type = "string", _mandatory = true }, + version = { _type = "string", _pattern = "[%w.]+-[%d]+", _mandatory = true }, + description = { + fields = { + summary = { _type = "string" }, + detailed = { _type = "string" }, + homepage = { _type = "string" }, + license = { _type = "string" }, + maintainer = { _type = "string" }, + }, + }, + dependencies = { + fields = { + platforms = type_check.MAGIC_PLATFORMS, + }, + _any = { + _type = "string", + _name = "a valid dependency string", + _pattern = "%s*([a-zA-Z0-9][a-zA-Z0-9%.%-%_]*)%s*([^/]*)", + }, + }, + supported_platforms = { + _any = { _type = "string" }, + }, + external_dependencies = { + fields = { + platforms = type_check.MAGIC_PLATFORMS, + }, + _any = { + fields = { + program = { _type = "string" }, + header = { _type = "string" }, + library = { _type = "string" }, + }, + }, + }, + source = { + _mandatory = true, + fields = { + platforms = type_check.MAGIC_PLATFORMS, + url = { _type = "string", _mandatory = true }, + md5 = { _type = "string" }, + file = { _type = "string" }, + dir = { _type = "string" }, + tag = { _type = "string" }, + branch = { _type = "string" }, + module = { _type = "string" }, + cvs_tag = { _type = "string" }, + cvs_module = { _type = "string" }, + }, + }, + build = { + fields = { + platforms = type_check.MAGIC_PLATFORMS, + type = { _type = "string" }, + install = { + fields = { + lua = { + _more = true, + }, + lib = { + _more = true, + }, + conf = { + _more = true, + }, + bin = { + _more = true, + }, + }, + }, + copy_directories = { + _any = { _type = "string" }, + }, + }, + _more = true, + _mandatory = true, + }, + hooks = { + fields = { + platforms = type_check.MAGIC_PLATFORMS, + post_install = { _type = "string" }, + }, + }, + }, + }, + + ["1.1"] = { + fields = { + deploy = { + fields = { + wrap_bin_scripts = { _type = "boolean" }, + }, + }, + }, + }, + + ["3.0"] = { + fields = { + description = { + fields = { + labels = { + _any = { _type = "string" }, + }, + issues_url = { _type = "string" }, + }, + }, + dependencies = { + _any = { + _pattern = "%s*([a-zA-Z0-9%.%-%_]*/?[a-zA-Z0-9][a-zA-Z0-9%.%-%_]*)%s*([^/]*)", + }, + }, + build_dependencies = { + fields = { + platforms = type_check.MAGIC_PLATFORMS, + }, + _any = { + _type = "string", + _name = "a valid dependency string", + _pattern = "%s*([a-zA-Z0-9%.%-%_]*/?[a-zA-Z0-9][a-zA-Z0-9%.%-%_]*)%s*([^/]*)", + }, + }, + test_dependencies = { + fields = { + platforms = type_check.MAGIC_PLATFORMS, + }, + _any = { + _type = "string", + _name = "a valid dependency string", + _pattern = "%s*([a-zA-Z0-9%.%-%_]*/?[a-zA-Z0-9][a-zA-Z0-9%.%-%_]*)%s*([^/]*)", + }, + }, + build = { + _mandatory = false, + }, + test = { + fields = { + platforms = type_check.MAGIC_PLATFORMS, + type = { _type = "string" }, + }, + _more = true, + }, + }, + }, +}) + + + + + + + + + +type_rockspec.order = { + "rockspec_format", + "package", + "version", + "source", + "description", + "supported_platforms", + "dependencies", + "build_dependencies", + "external_dependencies", + "build", + "test_dependencies", + "test", + "hooks", + sub_orders = { + ["source"] = { "url", "tag", "branch", "md5" }, + ["description"] = { "summary", "detailed", "homepage", "license" }, + ["build"] = { "type", "modules", "copy_directories", "platforms" }, + ["test"] = { "type" }, + }, +} + +local function check_rockspec_using_version(rockspec, globals, version) + local schema = rockspec_formats[version] + if not schema then + return nil, "unknown rockspec format " .. version + end + local ok, err = type_check.check_undeclared_globals(globals, schema) + if ok then + ok, err = type_check.type_check_table(version, rockspec, schema, "") + end + if ok then + return true + else + return nil, err + end +end + + + + + + + +function type_rockspec.check(rockspec, globals) + + local version = rockspec.rockspec_format or "1.0" + local ok, err = check_rockspec_using_version(rockspec, globals, version) + if ok then + return true + end + + + + + local found = false + for _, v in ipairs(versions) do + if not found then + if v == version then + found = true + end + else + local v_ok = check_rockspec_using_version(rockspec, globals, v) + if v_ok then + return nil, err .. " (using rockspec format " .. version .. " -- " .. + [[adding 'rockspec_format = "]] .. v .. [["' to the rockspec ]] .. + [[will fix this)]] + end + end + end + + return nil, err .. " (using rockspec format " .. version .. ")" +end + +return type_rockspec diff --git a/src/luarocks/type_check.lua b/src/luarocks/type_check.lua new file mode 100644 index 00000000..23305baf --- /dev/null +++ b/src/luarocks/type_check.lua @@ -0,0 +1,237 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local pairs = _tl_compat and _tl_compat.pairs or pairs; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table +local type_check = {TableSchema = {}, } + + + + + + + + + + + + + + + + +local cfg = require("luarocks.core.cfg") +local fun = require("luarocks.fun") +local util = require("luarocks.util") +local vers = require("luarocks.core.vers") + + + + + + +type_check.MAGIC_PLATFORMS = {} + +do + local function fill_in_version(tbl, version) + + if not tbl.fields then + return + end + + for _, v in pairs(tbl.fields) do + if type(v) == "table" then + if v._version == nil then + v._version = version + end + fill_in_version(v) + end + end + end + + local function expand_magic_platforms(tbl) + for k, v in pairs(tbl.fields) do + if v == type_check.MAGIC_PLATFORMS then + tbl.fields[k] = { + _any = util.deep_copy(tbl), + } + tbl.fields[k]._any.fields[k] = nil + expand_magic_platforms(v) + end + end + end + + + + + + + function type_check.declare_schemas(inputs) + local schemas = {} + local parent_version + + local versions = fun.reverse_in(fun.sort_in(util.keys(inputs), vers.compare_versions)) + + for _, version in ipairs(versions) do + local schema = inputs[version] + if parent_version then + local copy = util.deep_copy(schemas[parent_version]) + util.deep_merge(copy, schema) + schema = copy + end + fill_in_version(schema, version) + expand_magic_platforms(schema) + parent_version = version + schemas[version] = schema + end + + return schemas, versions + end +end + + + +local function check_version(version, typetbl, context) + local typetbl_version = typetbl._version or "1.0" + if vers.compare_versions(typetbl_version, version) then + if context == "" then + return nil, "Invalid rockspec_format version number in rockspec? Please fix rockspec accordingly." + else + return nil, context .. " is not supported in rockspec format " .. version .. " (requires version " .. typetbl_version .. "), please fix the rockspec_format field accordingly." + end + end + return true +end + + + + + + + + + + + + + + + +local function type_check_item(version, item, typetbl, context) + + if typetbl._version and typetbl._version ~= "1.0" then + local ok, err = check_version(version, typetbl, context) + if not ok then + return nil, err + end + end + + local expected_type = typetbl._type or "table" + + if expected_type == "number" then + if not tonumber(item) then + return nil, "Type mismatch on field " .. context .. ": expected a number" + end + elseif expected_type == "string" then + if not (type(item) == "string") then + return nil, "Type mismatch on field " .. context .. ": expected a string, got " .. type(item) + end + local pattern = typetbl._pattern + if pattern then + if not item:match("^" .. pattern .. "$") then + local what = typetbl._name or ("'" .. pattern .. "'") + return nil, "Type mismatch on field " .. context .. ": invalid value '" .. item .. "' does not match " .. what + end + end + elseif expected_type == "table" then + if not (type(item) == "table") then + return nil, "Type mismatch on field " .. context .. ": expected a table" + else + return type_check.type_check_table(version, item, typetbl, context) + end + elseif type(item) ~= expected_type then + return nil, "Type mismatch on field " .. context .. ": expected " .. expected_type + end + return true +end + +local function mkfield(context, field) + if context == "" then + return tostring(field) + elseif type(field) == "string" then + return context .. "." .. field + else + return context .. "[" .. tostring(field) .. "]" + end +end + + + + + + + + + + + + + + + + + + + + + + + +function type_check.type_check_table(version, tbl, typetbl, context) + + local ok, err = check_version(version, typetbl, context) + if not ok then + return nil, err + end + + if not typetbl.fields then + + return true + end + + for k, v in pairs(tbl) do + local t = typetbl.fields[tostring(k)] or typetbl._any + if t then + ok, err = type_check_item(version, v, t, mkfield(context, k)) + if not ok then return nil, err end + elseif typetbl._more then + + else + if not cfg.accept_unknown_fields then + return nil, "Unknown field " .. tostring(k) + end + end + end + + for k, v in pairs(typetbl.fields) do + if k:sub(1, 1) ~= "_" and v._mandatory then + if not tbl[k] then + return nil, "Mandatory field " .. mkfield(context, k) .. " is missing." + end + end + end + return true +end + +function type_check.check_undeclared_globals(globals, typetbl) + local undeclared = {} + for glob, _ in pairs(globals) do + if not (typetbl.fields[glob] or typetbl.fields["MUST_" .. glob]) then + table.insert(undeclared, glob) + end + end + if #undeclared == 1 then + return nil, "Unknown variable: " .. undeclared[1] + elseif #undeclared > 1 then + return nil, "Unknown variables: " .. table.concat(undeclared, ", ") + end + return true +end + +return type_check diff --git a/src/luarocks/upload/api.lua b/src/luarocks/upload/api.lua new file mode 100644 index 00000000..ba2de943 --- /dev/null +++ b/src/luarocks/upload/api.lua @@ -0,0 +1,300 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local io = _tl_compat and _tl_compat.io or io; local os = _tl_compat and _tl_compat.os or os; local package = _tl_compat and _tl_compat.package or package; local pairs = _tl_compat and _tl_compat.pairs or pairs; local pcall = _tl_compat and _tl_compat.pcall or pcall; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table; local api = {Configuration = {}, Api = {}, } + + + + + + + + + + + + + + + + + + + +local cfg = require("luarocks.core.cfg") +local fs = require("luarocks.fs") +local dir = require("luarocks.dir") +local util = require("luarocks.util") +local persist = require("luarocks.persist") +local multipart = require("luarocks.upload.multipart") +local json = require("luarocks.vendor.dkjson") +local dir_sep = package.config:sub(1, 1) + + +local Api = api.Api + + + + + + +local function upload_config_file() + if not cfg.config_files.user.file then + return nil + end + return (cfg.config_files.user.file:gsub("[\\/][^\\/]+$", dir_sep .. "upload_config.lua")) +end + +function api.Api:load_config() + local upload_conf = upload_config_file() + if not upload_conf then return nil end + local config = persist.load_into_table(upload_conf) + return config +end + +function api.Api:save_config() + + local res, errraw = self:raw_method("status") + if not res then + return nil, errraw + end + local reserrors = res.errors + if type(reserrors) == "table" then + return nil, ("Server error: " .. tostring(reserrors[1])) + end + local upload_conf = upload_config_file() + if not upload_conf then return nil end + local ok, errmake = fs.make_dir(dir.dir_name(upload_conf)) + if not ok then + return nil, errmake + end + persist.save_from_table(upload_conf, self.config) + fs.set_permissions(upload_conf, "read", "user") + return true +end + +function api.Api:check_version() + if not self._server_tool_version then + local tool_version = cfg.upload.tool_version + local res, err = self:request(tostring(self.config.server) .. "/api/tool_version", { + current = tool_version, + }) + if not res then + return nil, err + end + if not res.version then + return nil, "failed to fetch tool version" + end + self._server_tool_version = tostring(res.version) + if res.force_update then + return nil, "Your upload client is too out of date to continue, please upgrade LuaRocks." + end + if res.version ~= tool_version then + util.warning("your LuaRocks is out of date, consider upgrading.") + end + end + return true +end + +function api.Api:method(path, ...) + local res, err = self:raw_method(path, ...) + if not res then + return nil, err + end + local reserrors = res.errors + if type(reserrors) == "table" then + if reserrors[1] == "Invalid key" then + return nil, reserrors[1] .. " (use the --api-key flag to change)" + end + local msg = table.concat(reserrors, ", ") + return nil, "API Failed: " .. msg + end + return res +end + +function api.Api:raw_method(path, ...) + self:check_version() + local url = tostring(self.config.server) .. "/api/" .. tostring(cfg.upload.api_version) .. "/" .. tostring(self.config.key) .. "/" .. path + return self:request(url, ...) +end + +local function encode_query_string(t, sep) + if sep == nil then + sep = "&" + end + local i = 0 + local buf = {} + for k, v in pairs(t) do + local ks, vs + local vf = v + if type(vf) == "table" then + ks, vs = k, vf:content() + else + ks, vs = k, vf + end + buf[i + 1] = multipart.url_escape(ks) + buf[i + 2] = "=" + buf[i + 3] = multipart.url_escape(vs) + buf[i + 4] = sep + i = i + 4 + end + buf[i] = nil + return table.concat(buf) +end + +local function redact_api_url(url) + local urls = tostring(url) + return (urls:gsub(".*/api/[^/]+/[^/]+", "")) or "" +end + +local ltn12_ok, ltn12 = pcall(require, "ltn12") +if not ltn12_ok then + + api.Api.request = function(self, url, params, post_params) + local vars = cfg.variables + + if fs.which_tool("downloader") == "wget" then + local curl_ok, err = fs.is_tool_available(vars.CURL, "curl") + if not curl_ok then + return nil, err + end + end + + if not self.config.key then + return nil, "Must have API key before performing any actions." + end + if params and next(params) then + url = url .. ("?" .. encode_query_string(params)) + end + local method = "GET" + local out + local tmpfile = fs.tmpname() + if post_params then + method = "POST" + local curl_cmd = vars.CURL .. " " .. vars.CURLNOCERTFLAG .. " -f -L --silent --user-agent \"" .. cfg.user_agent .. " via curl\" " + for k, v in pairs(post_params) do + local var + if type(v) == "table" then + var = "@" .. v.fname + else + var = v + end + curl_cmd = curl_cmd .. "--form \"" .. k .. "=" .. var .. "\" " + end + if cfg.connection_timeout and cfg.connection_timeout > 0 then + curl_cmd = curl_cmd .. "--connect-timeout " .. tonumber(cfg.connection_timeout) .. " " + end + local ok = fs.execute_string(curl_cmd .. fs.Q(url) .. " -o " .. fs.Q(tmpfile)) + if not ok then + return nil, "API failure: " .. redact_api_url(url) + end + else + local name, err = fs.download(url, tmpfile) + if not name then + return nil, "API failure: " .. tostring(err) .. " - " .. redact_api_url(url) + end + end + + local tmpfd = io.open(tmpfile) + if not tmpfd then + os.remove(tmpfile) + return nil, "API failure reading temporary file - " .. redact_api_url(url) + end + out = tmpfd:read("*a") + tmpfd:close() + os.remove(tmpfile) + + if self.debug then + util.printout("[" .. tostring(method) .. " via curl] " .. redact_api_url(url) .. " ... ") + end + + return json.decode(out) + end + +else + + local warned_luasec = false + + api.Api.request = function(self, url, params, post_params) + local server = tostring(self.config.server) + + local http_ok, http + local via = "luasocket" + if server:match("^https://") then + http_ok, http = pcall(require, "ssl.https") + if http_ok then + via = "luasec" + else + if not warned_luasec then + util.printerr("LuaSec is not available; using plain HTTP. Install 'luasec' to enable HTTPS.") + warned_luasec = true + end + http_ok, http = pcall(require, "socket.http") + url = url:gsub("^https", "http") + via = "luasocket" + end + else + http_ok, http = pcall(require, "socket.http") + end + if not http_ok then + return nil, "Failed loading socket library!" + end + + if not self.config.key then + return nil, "Must have API key before performing any actions." + end + local body + local headers = {} + if params and next(params) then + url = url .. ("?" .. encode_query_string(params)) + end + if post_params then + local boundary + body, boundary = multipart.encode(post_params) + headers["Content-length"] = tostring(#body) + headers["Content-type"] = "multipart/form-data; boundary=" .. tostring(boundary) + end + local method = post_params and "POST" or "GET" + if self.debug then + util.printout("[" .. tostring(method) .. " via " .. via .. "] " .. redact_api_url(url) .. " ... ") + end + local out = {} + local _, status = http.request({ + url = url, + headers = headers, + method = method, + sink = ltn12.sink.table(out), + source = body and ltn12.source.string(body), + }) + if self.debug then + util.printout(tostring(status)) + end + local pok, ret = pcall(json.decode, table.concat(out)) + if pok and ret then + return ret + end + return nil, "API returned " .. tostring(status) .. " - " .. redact_api_url(url) + end + +end + +function api.new(args) + local self = {} + setmetatable(self, { __index = Api }) + self.config = self:load_config() or {} + self.config.server = args.server or self.config.server or cfg.upload.server + self.config.version = self.config.version or cfg.upload.version + self.config.key = args.temp_key or args.api_key or self.config.key + self.debug = args.debug + if not self.config.key then + return nil, "You need an API key to upload rocks.\n" .. + "Navigate to " .. self.config.server .. "/settings to get a key\n" .. + "and then pass it through the --api-key= flag." + end + if args.api_key then + local ok, err = self:save_config() + if not ok then + return nil, err + end + end + return self +end + +return api diff --git a/src/luarocks/upload/multipart.lua b/src/luarocks/upload/multipart.lua new file mode 100644 index 00000000..cf68bdef --- /dev/null +++ b/src/luarocks/upload/multipart.lua @@ -0,0 +1,117 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local io = _tl_compat and _tl_compat.io or io; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local math = _tl_compat and _tl_compat.math or math; local pairs = _tl_compat and _tl_compat.pairs or pairs; local pcall = _tl_compat and _tl_compat.pcall or pcall; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table; local _tl_table_unpack = unpack or table.unpack +local multipart = {File = {}, } + + + + + + + + + + + + + + +local File = multipart.File + + +function multipart.url_escape(s) + return (string.gsub(s, "([^A-Za-z0-9_])", function(c) + return string.format("%%%02x", string.byte(c)) + end)) +end + +function multipart.File:mime() + if not self.mimetype then + local mimetypes_ok, mimetypes = pcall(require, "mimetypes") + if mimetypes_ok then + self.mimetype = mimetypes.guess(self.fname) + end + self.mimetype = self.mimetype or "application/octet-stream" + end + return self.mimetype +end + +function multipart.File:content() + local fd = io.open(self.fname, "rb") + if not fd then + return nil, "Failed to open file: " .. self.fname + end + local data = fd:read("*a") + fd:close() + return data +end + +local function rand_string(len) + local shuffled = {} + for i = 1, len do + local r = math.random(97, 122) + if math.random() >= 0.5 then + r = r - 32 + end + shuffled[i] = r + end + return string.char(_tl_table_unpack(shuffled)) +end + + + + + + + + + +function multipart.encode(params) + local tuples = {} + for k, v in pairs(params) do + if type(k) == "string" then + table.insert(tuples, { k, v }) + end + end + local chunks = {} + for _, tuple in ipairs(tuples) do + local k, v = _tl_table_unpack(tuple) + k = multipart.url_escape(k) + local buffer = { 'Content-Disposition: form-data; name="' .. k .. '"' } + local content + if type(v) == "table" then + buffer[1] = buffer[1] .. ('; filename="' .. v.fname:gsub(".*[/\\]", "") .. '"') + table.insert(buffer, "Content-type: " .. v:mime()) + content = v:content() + else + content = v + end + table.insert(buffer, "") + table.insert(buffer, content) + table.insert(chunks, table.concat(buffer, "\r\n")) + end + local boundary + while not boundary do + boundary = "Boundary" .. rand_string(16) + for _, chunk in ipairs(chunks) do + if chunk:find(boundary) then + boundary = nil + break + end + end + end + local inner = "\r\n--" .. boundary .. "\r\n" + return table.concat({ "--", boundary, "\r\n", +table.concat(chunks, inner), +"\r\n", "--", boundary, "--", "\r\n", }), boundary +end + +function multipart.new_file(fname, mime) + local self = {} + + setmetatable(self, { __index = File }) + + self.fname = fname + self.mimetype = mime + return self +end + +return multipart diff --git a/src/luarocks/util.lua b/src/luarocks/util.lua new file mode 100644 index 00000000..7035f305 --- /dev/null +++ b/src/luarocks/util.lua @@ -0,0 +1,611 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local debug = _tl_compat and _tl_compat.debug or debug; local io = _tl_compat and _tl_compat.io or io; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local os = _tl_compat and _tl_compat.os or os; local package = _tl_compat and _tl_compat.package or package; local pairs = _tl_compat and _tl_compat.pairs or pairs; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table + + + + + +local core = require("luarocks.core.util") +local cfg = require("luarocks.core.cfg") + + + + +local util = {Fn = {}, } + + + + + + + + + + + + + + + + + + +util.cleanup_path = core.cleanup_path +util.split_string = core.split_string +util.sortedpairs = core.sortedpairs +util.deep_merge = core.deep_merge +util.deep_merge_under = core.deep_merge_under +util.popen_read = core.popen_read +util.show_table = core.show_table +util.printerr = core.printerr +util.warning = core.warning +util.keys = core.keys +util.matchquote = core.matchquote + + + + + + + +local scheduled_functions = {} + + + + + + + + +function util.schedule_function(f, ...) + local item = { fn = f, args = _tl_table_pack(...) } + table.insert(scheduled_functions, item) + return item +end + + + + + +function util.remove_scheduled_function(item) + for k, v in ipairs(scheduled_functions) do + if v == item then + table.remove(scheduled_functions, k) + return + end + end +end + + + + + + +function util.run_scheduled_functions() + local fs = require("luarocks.fs") + if fs.change_dir_to_root then + fs.change_dir_to_root() + end + for i = #scheduled_functions, 1, -1 do + local item = scheduled_functions[i] + item.fn(_tl_table_unpack(item.args, 1, item.args.n)) + end +end + +local var_format_pattern = "%$%((%a[%a%d_]+)%)" + + + + + + + + + + + +function util.warn_if_not_used(var_defs, needed_set, msg) + local seen = {} + for _, val in pairs(var_defs) do + for used in val:gmatch(var_format_pattern) do + seen[used] = true + end + end + for var, _ in pairs(needed_set) do + if not seen[var] then + util.warning(msg:format(var)) + end + end +end + + + + +local function warn_failed_matches(line) + local any_failed = false + if line:match(var_format_pattern) then + for unmatched in line:gmatch(var_format_pattern) do + util.warning("unmatched variable " .. unmatched) + any_failed = true + end + end + return any_failed +end + + + + + + + + + +function util.variable_substitutions(tbl, vars) + + local updated = {} + for k, v in pairs(tbl) do + if type(v) == "string" then + updated[k] = string.gsub(v, var_format_pattern, vars) + if warn_failed_matches(updated[k]) then + updated[k] = updated[k]:gsub(var_format_pattern, "") + end + end + end + for k, v in pairs(updated) do + tbl[k] = v + end +end + +function util.lua_versions(sort) + local versions = { "5.1", "5.2", "5.3", "5.4" } + local i = 0 + if sort == "descending" then + i = #versions + 1 + return function() + i = i - 1 + return versions[i] + end + else + return function() + i = i + 1 + return versions[i] + end + end +end + +function util.lua_path_variables() + local lpath_var = "LUA_PATH" + local lcpath_var = "LUA_CPATH" + + local lv = cfg.lua_version:gsub("%.", "_") + if lv ~= "5_1" then + if os.getenv("LUA_PATH_" .. lv) then + lpath_var = "LUA_PATH_" .. lv + end + if os.getenv("LUA_CPATH_" .. lv) then + lcpath_var = "LUA_CPATH_" .. lv + end + end + return lpath_var, lcpath_var +end + +function util.starts_with(s, prefix) + return s:sub(1, #prefix) == prefix +end + + +function util.printout(...) + io.stdout:write(table.concat({ ... }, "\t")) + io.stdout:write("\n") +end + +function util.title(msg, porcelain, underline) + if porcelain then return end + util.printout() + util.printout(msg) + util.printout((underline or "-"):rep(#msg)) + util.printout() +end + +function util.this_program(default) + local i = 1 + local last, cur = default, default + while i do + local dbg = debug and debug.getinfo(i, "S") + if not dbg then break end + last = cur + cur = dbg.source + i = i + 1 + end + local prog = last:sub(1, 1) == "@" and last:sub(2) or last + + + local lrdir, binpath = prog:match("^(.*)/lib/luarocks/rocks%-[0-9.]*/[^/]+/[^/]+(/bin/[^/]+)$") + if lrdir then + + return lrdir .. binpath + end + + return prog +end + +function util.format_rock_name(name, namespace, version) + return (namespace and namespace .. "/" or "") .. name .. (version and " " .. version or "") +end + +function util.deps_mode_option(parser, program) + + parser:option("--deps-mode", "How to handle dependencies. Four modes are supported:\n" .. + "* all - use all trees from the rocks_trees list for finding dependencies\n" .. + "* one - use only the current tree (possibly set with --tree)\n" .. + "* order - use trees based on order (use the current tree and all " .. + "trees below it on the rocks_trees list)\n" .. + "* none - ignore dependencies altogether.\n" .. + "The default mode may be set with the deps_mode entry in the configuration file.\n" .. + 'The current default is "' .. cfg.deps_mode .. '".\n' .. + "Type '" .. util.this_program(program or "luarocks") .. "' with no " .. + "arguments to see your list of rocks trees."): + argname(""): + choices({ "all", "one", "order", "none" }) + parser:flag("--nodeps"):hidden(true) +end + +function util.see_help(command, program) + return "See '" .. util.this_program(program or "luarocks") .. ' help' .. (command and " " .. command or "") .. "'." +end + +function util.see_also(text) + local see_also = "See also:\n" + if text then + see_also = see_also .. text .. "\n" + end + return see_also .. " '" .. util.this_program("luarocks") .. " help' for general options and configuration." +end + +function util.announce_install(rockspec) + local path = require("luarocks.path") + + local suffix = "" + if rockspec.description and rockspec.description.license then + suffix = " (license: " .. rockspec.description.license .. ")" + end + + util.printout(rockspec.name .. " " .. rockspec.version .. " is now installed in " .. path.root_dir(cfg.root_dir) .. suffix) + util.printout() +end + + + + + + + +local function collect_rockspecs(versions, paths, unnamed_paths, subdir) + local fs = require("luarocks.fs") + local dir = require("luarocks.dir") + local path = require("luarocks.path") + local vers = require("luarocks.core.vers") + if fs.is_dir(subdir) then + for file in fs.dir(subdir) do + file = dir.path(subdir, file) + + if file:match("rockspec$") and fs.is_file(file) then + local rock, version = path.parse_name(file) + + if rock then + if not versions[rock] or vers.compare_versions(version, versions[rock]) then + versions[rock] = version + paths[rock] = file + end + else + table.insert(unnamed_paths, file) + end + end + end + end +end + + + +function util.get_default_rockspec() + + local versions = {} + local paths = {} + local unnamed_paths = {} + + collect_rockspecs(versions, paths, unnamed_paths, ".") + collect_rockspecs(versions, paths, unnamed_paths, "rockspec") + collect_rockspecs(versions, paths, unnamed_paths, "rockspecs") + + if #unnamed_paths > 0 then + + + if #unnamed_paths > 1 then + return nil, "Please specify which rockspec file to use." + else + return unnamed_paths[1] + end + else + local fs = require("luarocks.fs") + local dir = require("luarocks.dir") + local basename = dir.base_name(fs.current_dir()) + + if paths[basename] then + return paths[basename] + end + + local rock = next(versions) + + if rock then + + if next(versions, rock) then + return nil, "Please specify which rockspec file to use." + else + return paths[rock] + end + else + return nil, "Argument missing: please specify a rockspec to use on current directory." + end + end +end + + + + +function util.LQ(s) + return ("%q"):format(s) +end + + + + +function util.split_namespace(ns_name) + local p1, p2 = ns_name:match("^([^/]+)/([^/]+)$") + if p1 then + return p2, p1 + end + return ns_name +end + + +function util.namespaced_name_action(args, target, ns_name) + + if not ns_name then + return + end + + if ns_name:match("%.rockspec$") or ns_name:match("%.rock$") then + args[target] = ns_name + else + local name, namespace = util.split_namespace(ns_name) + args[target] = name:lower() + if namespace then + args.namespace = namespace:lower() + end + end +end + +function util.deep_copy(tbl) + local copy = {} + for k, v in pairs(tbl) do + if type(v) == "table" then + copy[k] = util.deep_copy(v) + else + copy[k] = v + end + end + return copy +end + + + + +function util.exists(file) + local fd, _, code = io.open(file, "r") + if code == 13 then + + + return true + end + if fd then + fd:close() + return true + end + return false +end + +function util.lua_is_wrapper(interp) + local fd, err = io.open(interp, "r") + if not fd then + return nil, err + end + local data + data, err = fd:read(1000) + fd:close() + if not data then + return nil, err + end + return not not data:match("LUAROCKS_SYSCONFDIR") +end + +do + local function Q(pathname) + if pathname:match("^.:") then + return pathname:sub(1, 2) .. '"' .. pathname:sub(3) .. '"' + end + return '"' .. pathname .. '"' + end + + function util.check_lua_version(lua, luaver) + if not util.exists(lua) then + return nil + end + local lv = util.popen_read(Q(lua) .. ' -e "io.write(_VERSION:sub(5))"') + if lv == "" then + return nil + end + if luaver and luaver ~= lv then + return nil + end + return lv + end + + function util.get_luajit_version() + if cfg.cache.luajit_version_checked then + return cfg.cache.luajit_version + end + cfg.cache.luajit_version_checked = true + + if not cfg.variables.LUA then + return nil + end + + local ljv + if cfg.lua_version == "5.1" then + + ljv = util.popen_read(Q(cfg.variables.LUA) .. ' -e "io.write(tostring(jit and jit.version:gsub([[^%S+ (%S+).*]], [[%1]])))"') + if ljv == "nil" then + ljv = nil + end + end + cfg.cache.luajit_version = ljv + return ljv + end + + local find_lua_bindir + do + local exe_suffix = (package.config:sub(1, 1) == "\\" and ".exe" or "") + + local function insert_lua_variants(names, luaver) + local variants = { + "lua" .. luaver .. exe_suffix, + "lua" .. luaver:gsub("%.", "") .. exe_suffix, + "lua-" .. luaver .. exe_suffix, + "lua-" .. luaver:gsub("%.", "") .. exe_suffix, + } + for _, name in ipairs(variants) do + table.insert(names, name) + end + end + + find_lua_bindir = function(prefix, luaver, verbose) + local names = {} + if luaver then + insert_lua_variants(names, luaver) + else + for v in util.lua_versions("descending") do + insert_lua_variants(names, v) + end + end + if luaver == "5.1" or not luaver then + table.insert(names, "luajit" .. exe_suffix) + end + table.insert(names, "lua" .. exe_suffix) + + local tried = {} + local dir_sep = package.config:sub(1, 1) + for _, d in ipairs({ prefix .. dir_sep .. "bin", prefix }) do + for _, name in ipairs(names) do + local lua = d .. dir_sep .. name + local is_wrapper, err = util.lua_is_wrapper(lua) + if is_wrapper == false then + local lv = util.check_lua_version(lua, luaver) + if lv then + return lua, d, lv + end + elseif is_wrapper == true or err == nil then + table.insert(tried, lua) + else + table.insert(tried, string.format("%-13s (%s)", lua, err)) + end + end + end + local interp = luaver and + ("Lua " .. luaver .. " interpreter") or + "Lua interpreter" + return nil, interp .. " not found at " .. prefix .. "\n" .. + (verbose and "Tried:\t" .. table.concat(tried, "\n\t") or "") + end + end + + function util.find_lua(prefix, luaver, verbose) + local lua, bindir + lua, bindir, luaver = find_lua_bindir(prefix, luaver, verbose) + if not lua then + return nil, bindir + end + + return { + lua_version = luaver, + lua = lua, + lua_dir = prefix, + lua_bindir = bindir, + } + end +end + + + + + + + + + +function util.get_rocks_provided(rockspec) + + if not rockspec and cfg.cache.rocks_provided then + return cfg.cache.rocks_provided + end + + local rocks_provided = {} + + local lv = cfg.lua_version + + rocks_provided["lua"] = lv .. "-1" + + if lv == "5.2" then + rocks_provided["bit32"] = lv .. "-1" + end + + if lv == "5.3" or lv == "5.4" then + rocks_provided["utf8"] = lv .. "-1" + end + + if lv == "5.1" then + local ljv = util.get_luajit_version() + if ljv then + rocks_provided["luabitop"] = ljv .. "-1" + if (not rockspec) or rockspec:format_is_at_least("3.0") then + rocks_provided["luajit"] = ljv .. "-1" + end + end + end + + if cfg.rocks_provided then + util.deep_merge_under(rocks_provided, cfg.rocks_provided) + end + + if not rockspec then + cfg.cache.rocks_provided = rocks_provided + end + + return rocks_provided +end + +function util.remove_doc_dir(name, version) + local path = require("luarocks.path") + local fs = require("luarocks.fs") + local dir = require("luarocks.dir") + + local install_dir = path.install_dir(name, version) + for _, f in ipairs(fs.list_dir(install_dir)) do + local doc_dirs = { "doc", "docs" } + for _, d in ipairs(doc_dirs) do + if f == d then + fs.delete(dir.path(install_dir, f)) + end + end + end +end + +return util -- cgit v1.2.3-55-g6feb