summaryrefslogtreecommitdiff
path: root/src/luarocks/core
diff options
context:
space:
mode:
authorMike Vink <mike@pionative.com>2025-02-03 21:29:42 +0100
committerMike Vink <mike@pionative.com>2025-02-03 21:29:42 +0100
commit5155816b7b925dec5d5feb1568b1d7ceb00938b9 (patch)
treedeca28ea15e79f6f804c3d90d2ba757881638af5 /src/luarocks/core
fetch tarballHEADmaster
Diffstat (limited to 'src/luarocks/core')
-rw-r--r--src/luarocks/core/cfg.lua940
-rw-r--r--src/luarocks/core/dir.lua98
-rw-r--r--src/luarocks/core/manif.lua114
-rw-r--r--src/luarocks/core/path.lua157
-rw-r--r--src/luarocks/core/persist.lua81
-rw-r--r--src/luarocks/core/sysdetect.lua419
-rw-r--r--src/luarocks/core/util.lua322
-rw-r--r--src/luarocks/core/vers.lua207
8 files changed, 2338 insertions, 0 deletions
diff --git a/src/luarocks/core/cfg.lua b/src/luarocks/core/cfg.lua
new file mode 100644
index 0000000..9cfd9dd
--- /dev/null
+++ b/src/luarocks/core/cfg.lua
@@ -0,0 +1,940 @@
+
+--- Configuration for LuaRocks.
+-- Tries to load the user's configuration file and
+-- defines defaults for unset values. See the
+-- <a href="http://luarocks.org/en/Config_file_format">config
+-- file format documentation</a> for details.
+--
+-- End-users shouldn't edit this file. They can override any defaults
+-- set in this file using their system-wide or user-specific configuration
+-- files. Run `luarocks` with no arguments to see the locations of
+-- these files in your platform.
+
+local table, pairs, require, os, pcall, ipairs, package, type, assert =
+ table, pairs, require, os, pcall, ipairs, package, type, assert
+
+local dir = require("luarocks.core.dir")
+local util = require("luarocks.core.util")
+local persist = require("luarocks.core.persist")
+local sysdetect = require("luarocks.core.sysdetect")
+local vers = require("luarocks.core.vers")
+
+--------------------------------------------------------------------------------
+
+local program_version = "3.11.1"
+
+local is_windows = package.config:sub(1,1) == "\\"
+
+-- Set order for platform overrides.
+-- More general platform identifiers should be listed first,
+-- more specific ones later.
+local platform_order = {
+ -- Unixes
+ "unix",
+ "bsd",
+ "solaris",
+ "netbsd",
+ "openbsd",
+ "freebsd",
+ "dragonfly",
+ "linux",
+ "macosx",
+ "cygwin",
+ "msys",
+ "haiku",
+ -- Windows
+ "windows",
+ "win32",
+ "mingw",
+ "mingw32",
+ "msys2_mingw_w64",
+}
+
+local function detect_sysconfdir()
+ if not debug then
+ return
+ end
+ local src = debug.getinfo(1, "S").source
+ if not src then
+ return
+ end
+ src = dir.normalize(src)
+ if src:sub(1, 1) == "@" then
+ src = src:sub(2)
+ end
+ local basedir = src:match("^(.*)[\\/]luarocks[\\/]core[\\/]cfg.lua$")
+ if not basedir then
+ return
+ end
+ -- If installed in a Unix-like tree, use a Unix-like sysconfdir
+ local installdir = basedir:match("^(.*)[\\/]share[\\/]lua[\\/][^/]*$")
+ if installdir then
+ if installdir == "/usr" then
+ return "/etc/luarocks"
+ end
+ return dir.path(installdir, "etc", "luarocks")
+ end
+ -- Otherwise, use base directory of sources
+ return basedir
+end
+
+local load_config_file
+do
+ -- Create global environment for the config files;
+ local function env_for_config_file(cfg, platforms)
+ local platforms_copy = {}
+ for k,v in pairs(platforms) do
+ platforms_copy[k] = v
+ end
+
+ local e
+ e = {
+ home = cfg.home,
+ lua_version = cfg.lua_version,
+ platforms = platforms_copy,
+ processor = cfg.target_cpu, -- remains for compat reasons
+ target_cpu = cfg.target_cpu, -- replaces `processor`
+ os_getenv = os.getenv,
+ variables = cfg.variables or {},
+ dump_env = function()
+ -- debug function, calling it from a config file will show all
+ -- available globals to that config file
+ print(util.show_table(e, "global environment"))
+ end,
+ }
+ return e
+ end
+
+ -- Merge values from config files read into the `cfg` table
+ local function merge_overrides(cfg, overrides)
+ -- remove some stuff we do not want to integrate
+ overrides.os_getenv = nil
+ overrides.dump_env = nil
+ -- remove tables to be copied verbatim instead of deeply merged
+ if overrides.rocks_trees then cfg.rocks_trees = nil end
+ if overrides.rocks_servers then cfg.rocks_servers = nil end
+ -- perform actual merge
+ util.deep_merge(cfg, overrides)
+ end
+
+ local function update_platforms(platforms, overrides)
+ if overrides[1] then
+ for k, _ in pairs(platforms) do
+ platforms[k] = nil
+ end
+ for _, v in ipairs(overrides) do
+ platforms[v] = true
+ end
+ -- set some fallback default in case the user provides an incomplete configuration.
+ -- LuaRocks expects a set of defaults to be available.
+ if not (platforms.unix or platforms.windows) then
+ platforms[is_windows and "windows" or "unix"] = true
+ end
+ end
+ end
+
+ -- Load config file and merge its contents into the `cfg` module table.
+ -- @return filepath of successfully loaded file or nil if it failed
+ load_config_file = function(cfg, platforms, filepath)
+ local result, err, errcode = persist.load_into_table(filepath, env_for_config_file(cfg, platforms))
+ if (not result) and errcode ~= "open" then
+ -- errcode is either "load" or "run"; bad config file, so error out
+ return nil, err, "config"
+ end
+ if result then
+ -- success in loading and running, merge contents and exit
+ update_platforms(platforms, result.platforms)
+ result.platforms = nil
+ merge_overrides(cfg, result)
+ return filepath
+ end
+ return nil -- nothing was loaded
+ end
+end
+
+local platform_sets = {
+ freebsd = { unix = true, bsd = true, freebsd = true },
+ openbsd = { unix = true, bsd = true, openbsd = true },
+ dragonfly = { unix = true, bsd = true, dragonfly = true },
+ solaris = { unix = true, solaris = true },
+ windows = { windows = true, win32 = true },
+ cygwin = { unix = true, cygwin = true },
+ macosx = { unix = true, bsd = true, macosx = true, macos = true },
+ netbsd = { unix = true, bsd = true, netbsd = true },
+ haiku = { unix = true, haiku = true },
+ linux = { unix = true, linux = true },
+ mingw = { windows = true, win32 = true, mingw32 = true, mingw = true },
+ msys = { unix = true, cygwin = true, msys = true },
+ msys2_mingw_w64 = { windows = true, win32 = true, mingw32 = true, mingw = true, msys = true, msys2_mingw_w64 = true },
+}
+
+local function make_platforms(system)
+ -- fallback to Unix in unknown systems
+ return platform_sets[system] or { unix = true }
+end
+
+--------------------------------------------------------------------------------
+
+local function make_defaults(lua_version, target_cpu, platforms, home)
+
+ -- Configure defaults:
+ local defaults = {
+
+ local_by_default = false,
+ accept_unknown_fields = false,
+ fs_use_modules = true,
+ hooks_enabled = true,
+ deps_mode = "one",
+ no_manifest = false,
+ check_certificates = false,
+
+ cache_timeout = 60,
+ cache_fail_timeout = 86400,
+
+ lua_modules_path = dir.path("share", "lua", lua_version),
+ lib_modules_path = dir.path("lib", "lua", lua_version),
+ rocks_subdir = dir.path("lib", "luarocks", "rocks-"..lua_version),
+
+ arch = "unknown",
+ lib_extension = "unknown",
+ obj_extension = "unknown",
+ link_lua_explicitly = false,
+
+ rocks_servers = {
+ {
+ "https://luarocks.org",
+ "https://raw.githubusercontent.com/rocks-moonscript-org/moonrocks-mirror/master/",
+ "https://loadk.com/luarocks/",
+ }
+ },
+ disabled_servers = {},
+
+ upload = {
+ server = "https://luarocks.org",
+ tool_version = "1.0.0",
+ api_version = "1",
+ },
+
+ lua_extension = "lua",
+ connection_timeout = 30, -- 0 = no timeout
+
+ variables = {
+ MAKE = os.getenv("MAKE") or "make",
+ CC = os.getenv("CC") or "cc",
+ LD = os.getenv("CC") or "ld",
+ AR = os.getenv("AR") or "ar",
+ RANLIB = os.getenv("RANLIB") or "ranlib",
+
+ CVS = "cvs",
+ GIT = "git",
+ SSCM = "sscm",
+ SVN = "svn",
+ HG = "hg",
+
+ GPG = "gpg",
+
+ RSYNC = "rsync",
+ WGET = "wget",
+ SCP = "scp",
+ CURL = "curl",
+
+ PWD = "pwd",
+ MKDIR = "mkdir",
+ RMDIR = "rmdir",
+ CP = "cp",
+ LN = "ln",
+ LS = "ls",
+ RM = "rm",
+ FIND = "find",
+ CHMOD = "chmod",
+ ICACLS = "icacls",
+ MKTEMP = "mktemp",
+
+ ZIP = "zip",
+ UNZIP = "unzip -n",
+ GUNZIP = "gunzip",
+ BUNZIP2 = "bunzip2",
+ TAR = "tar",
+
+ MD5SUM = "md5sum",
+ OPENSSL = "openssl",
+ MD5 = "md5",
+ TOUCH = "touch",
+
+ CMAKE = "cmake",
+ SEVENZ = "7z",
+
+ RSYNCFLAGS = "--exclude=.git -Oavz",
+ CURLNOCERTFLAG = "",
+ WGETNOCERTFLAG = "",
+ },
+
+ external_deps_subdirs = {
+ bin = "bin",
+ lib = "lib",
+ include = "include"
+ },
+ runtime_external_deps_subdirs = {
+ bin = "bin",
+ lib = "lib",
+ include = "include"
+ },
+ }
+
+ if platforms.windows then
+
+ defaults.arch = "win32-"..target_cpu
+ defaults.lib_extension = "dll"
+ defaults.external_lib_extension = "dll"
+ defaults.static_lib_extension = "lib"
+ defaults.obj_extension = "obj"
+ defaults.external_deps_dirs = {
+ dir.path("c:", "external"),
+ dir.path("c:", "windows", "system32"),
+ }
+
+ defaults.makefile = "Makefile.win"
+ defaults.variables.PWD = "echo %cd%"
+ defaults.variables.MKDIR = "md"
+ defaults.variables.MAKE = os.getenv("MAKE") or "nmake"
+ defaults.variables.CC = os.getenv("CC") or "cl"
+ defaults.variables.RC = os.getenv("WINDRES") or "rc"
+ defaults.variables.LD = os.getenv("LINK") or "link"
+ defaults.variables.MT = os.getenv("MT") or "mt"
+ defaults.variables.AR = os.getenv("AR") or "lib"
+ defaults.variables.CFLAGS = os.getenv("CFLAGS") or "/nologo /MD /O2"
+ defaults.variables.LDFLAGS = os.getenv("LDFLAGS")
+ defaults.variables.LIBFLAG = "/nologo /dll"
+
+ defaults.external_deps_patterns = {
+ bin = { "?.exe", "?.bat" },
+ lib = { "?.lib", "lib?.lib", "?.dll", "lib?.dll" },
+ include = { "?.h" }
+ }
+ defaults.runtime_external_deps_patterns = {
+ bin = { "?.exe", "?.bat" },
+ lib = { "?.dll", "lib?.dll" },
+ include = { "?.h" }
+ }
+ defaults.export_path_separator = ";"
+ defaults.wrapper_suffix = ".bat"
+
+ local localappdata = os.getenv("LOCALAPPDATA")
+ if not localappdata then
+ -- for Windows versions below Vista
+ localappdata = dir.path((os.getenv("USERPROFILE") or dir.path("c:", "Users", "All Users")), "Local Settings", "Application Data")
+ end
+ defaults.local_cache = dir.path(localappdata, "LuaRocks", "Cache")
+ defaults.web_browser = "start"
+
+ defaults.external_deps_subdirs.lib = { "lib", "", "bin" }
+ defaults.runtime_external_deps_subdirs.lib = { "lib", "", "bin" }
+ defaults.link_lua_explicitly = true
+ defaults.fs_use_modules = false
+ end
+
+ if platforms.mingw32 then
+ defaults.obj_extension = "o"
+ defaults.static_lib_extension = "a"
+ defaults.external_deps_dirs = {
+ dir.path("c:", "external"),
+ dir.path("c:", "mingw"),
+ dir.path("c:", "windows", "system32"),
+ }
+ defaults.cmake_generator = "MinGW Makefiles"
+ defaults.variables.MAKE = os.getenv("MAKE") or "mingw32-make"
+ if target_cpu == "x86_64" then
+ defaults.variables.CC = os.getenv("CC") or "x86_64-w64-mingw32-gcc"
+ defaults.variables.LD = os.getenv("CC") or "x86_64-w64-mingw32-gcc"
+ else
+ defaults.variables.CC = os.getenv("CC") or "mingw32-gcc"
+ defaults.variables.LD = os.getenv("CC") or "mingw32-gcc"
+ end
+ defaults.variables.AR = os.getenv("AR") or "ar"
+ defaults.variables.RC = os.getenv("WINDRES") or "windres"
+ defaults.variables.RANLIB = os.getenv("RANLIB") or "ranlib"
+ defaults.variables.CFLAGS = os.getenv("CFLAGS") or "-O2"
+ defaults.variables.LDFLAGS = os.getenv("LDFLAGS")
+ defaults.variables.LIBFLAG = "-shared"
+ defaults.makefile = "Makefile"
+ defaults.external_deps_patterns = {
+ bin = { "?.exe", "?.bat" },
+ -- mingw lookup list from http://stackoverflow.com/a/15853231/1793220
+ -- ...should we keep ?.lib at the end? It's not in the above list.
+ lib = { "lib?.dll.a", "?.dll.a", "lib?.a", "cyg?.dll", "lib?.dll", "?.dll", "?.lib" },
+ include = { "?.h" }
+ }
+ defaults.runtime_external_deps_patterns = {
+ bin = { "?.exe", "?.bat" },
+ lib = { "cyg?.dll", "?.dll", "lib?.dll" },
+ include = { "?.h" }
+ }
+ defaults.link_lua_explicitly = true
+ end
+
+ if platforms.unix then
+ defaults.lib_extension = "so"
+ defaults.static_lib_extension = "a"
+ defaults.external_lib_extension = "so"
+ defaults.obj_extension = "o"
+ defaults.external_deps_dirs = { "/usr/local", "/usr", "/" }
+
+ defaults.variables.CFLAGS = os.getenv("CFLAGS") or "-O2"
+ -- we pass -fPIC via CFLAGS because of old Makefile-based Lua projects
+ -- which didn't have -fPIC in their Makefiles but which honor CFLAGS
+ if not defaults.variables.CFLAGS:match("-fPIC") then
+ defaults.variables.CFLAGS = defaults.variables.CFLAGS.." -fPIC"
+ end
+
+ defaults.variables.LDFLAGS = os.getenv("LDFLAGS")
+
+ defaults.cmake_generator = "Unix Makefiles"
+ defaults.variables.CC = os.getenv("CC") or "gcc"
+ defaults.variables.LD = os.getenv("CC") or "gcc"
+ defaults.gcc_rpath = true
+ defaults.variables.LIBFLAG = "-shared"
+ defaults.variables.TEST = "test"
+
+ defaults.external_deps_patterns = {
+ bin = { "?" },
+ lib = { "lib?.a", "lib?.so", "lib?.so.*" },
+ include = { "?.h" }
+ }
+ defaults.runtime_external_deps_patterns = {
+ bin = { "?" },
+ lib = { "lib?.so", "lib?.so.*" },
+ include = { "?.h" }
+ }
+ defaults.export_path_separator = ":"
+ defaults.wrapper_suffix = ""
+ local xdg_cache_home = os.getenv("XDG_CACHE_HOME") or home.."/.cache"
+ defaults.local_cache = xdg_cache_home.."/luarocks"
+ defaults.web_browser = "xdg-open"
+ end
+
+ if platforms.cygwin then
+ defaults.lib_extension = "so" -- can be overridden in the config file for mingw builds
+ defaults.arch = "cygwin-"..target_cpu
+ defaults.cmake_generator = "Unix Makefiles"
+ defaults.variables.CC = "echo -llua | xargs " .. (os.getenv("CC") or "gcc")
+ defaults.variables.LD = "echo -llua | xargs " .. (os.getenv("CC") or "gcc")
+ defaults.variables.LIBFLAG = "-shared"
+ defaults.link_lua_explicitly = true
+ end
+
+ if platforms.msys then
+ -- msys is basically cygwin made out of mingw, meaning the subsytem is unixish
+ -- enough, yet we can freely mix with native win32
+ defaults.external_deps_patterns = {
+ bin = { "?.exe", "?.bat", "?" },
+ lib = { "lib?.so", "lib?.so.*", "lib?.dll.a", "?.dll.a",
+ "lib?.a", "lib?.dll", "?.dll", "?.lib" },
+ include = { "?.h" }
+ }
+ defaults.runtime_external_deps_patterns = {
+ bin = { "?.exe", "?.bat" },
+ lib = { "lib?.so", "?.dll", "lib?.dll" },
+ include = { "?.h" }
+ }
+ if platforms.mingw then
+ -- MSYS2 can build Windows programs that depend on
+ -- msys-2.0.dll (based on Cygwin) but MSYS2 is also designed
+ -- for building native Windows programs by MinGW. These
+ -- programs don't depend on msys-2.0.dll.
+ local pipe = io.popen("cygpath --windows %MINGW_PREFIX%")
+ local mingw_prefix = pipe:read("*l")
+ pipe:close()
+ defaults.external_deps_dirs = {
+ mingw_prefix,
+ dir.path("c:", "windows", "system32"),
+ }
+ defaults.makefile = "Makefile"
+ defaults.cmake_generator = "MSYS Makefiles"
+ defaults.local_cache = dir.path(home, ".cache", "luarocks")
+ defaults.variables.MAKE = os.getenv("MAKE") or "make"
+ defaults.variables.CC = os.getenv("CC") or "gcc"
+ defaults.variables.RC = os.getenv("WINDRES") or "windres"
+ defaults.variables.LD = os.getenv("CC") or "gcc"
+ defaults.variables.MT = os.getenv("MT") or nil
+ defaults.variables.AR = os.getenv("AR") or "ar"
+ defaults.variables.RANLIB = os.getenv("RANLIB") or "ranlib"
+
+ defaults.variables.CFLAGS = os.getenv("CFLAGS") or "-O2 -fPIC"
+ if not defaults.variables.CFLAGS:match("-fPIC") then
+ defaults.variables.CFLAGS = defaults.variables.CFLAGS.." -fPIC"
+ end
+
+ defaults.variables.LIBFLAG = "-shared"
+ end
+ end
+
+ if platforms.bsd then
+ defaults.variables.MAKE = "gmake"
+ defaults.gcc_rpath = false
+ defaults.variables.CC = os.getenv("CC") or "cc"
+ defaults.variables.LD = os.getenv("CC") or defaults.variables.CC
+ end
+
+ if platforms.macosx then
+ defaults.variables.MAKE = os.getenv("MAKE") or "make"
+ defaults.external_lib_extension = "dylib"
+ defaults.arch = "macosx-"..target_cpu
+ defaults.variables.LIBFLAG = "-bundle -undefined dynamic_lookup -all_load"
+ local version = util.popen_read("sw_vers -productVersion")
+ if not (version:match("^%d+%.%d+%.%d+$") or version:match("^%d+%.%d+$")) then
+ version = "10.3"
+ end
+ version = vers.parse_version(version)
+ if version >= vers.parse_version("11.0") then
+ version = vers.parse_version("11.0")
+ elseif version >= vers.parse_version("10.10") then
+ version = vers.parse_version("10.8")
+ elseif version >= vers.parse_version("10.5") then
+ version = vers.parse_version("10.5")
+ else
+ defaults.gcc_rpath = false
+ end
+ defaults.variables.CC = "env MACOSX_DEPLOYMENT_TARGET="..tostring(version).." gcc"
+ defaults.variables.LD = "env MACOSX_DEPLOYMENT_TARGET="..tostring(version).." gcc"
+ defaults.web_browser = "open"
+
+ -- XCode SDK
+ local sdk_path = util.popen_read("xcrun --show-sdk-path 2>/dev/null")
+ if sdk_path then
+ table.insert(defaults.external_deps_dirs, sdk_path .. "/usr")
+ table.insert(defaults.external_deps_patterns.lib, 1, "lib?.tbd")
+ table.insert(defaults.runtime_external_deps_patterns.lib, 1, "lib?.tbd")
+ end
+
+ -- Homebrew
+ table.insert(defaults.external_deps_dirs, "/usr/local/opt")
+ defaults.external_deps_subdirs.lib = { "lib", "" }
+ defaults.runtime_external_deps_subdirs.lib = { "lib", "" }
+ table.insert(defaults.external_deps_patterns.lib, 1, "/?/lib/lib?.dylib")
+ table.insert(defaults.runtime_external_deps_patterns.lib, 1, "/?/lib/lib?.dylib")
+ end
+
+ if platforms.linux then
+ defaults.arch = "linux-"..target_cpu
+
+ local gcc_arch = util.popen_read("gcc -print-multiarch 2>/dev/null")
+ if gcc_arch and gcc_arch ~= "" then
+ defaults.external_deps_subdirs.lib = { "lib/" .. gcc_arch, "lib64", "lib" }
+ defaults.runtime_external_deps_subdirs.lib = { "lib/" .. gcc_arch, "lib64", "lib" }
+ else
+ defaults.external_deps_subdirs.lib = { "lib64", "lib" }
+ defaults.runtime_external_deps_subdirs.lib = { "lib64", "lib" }
+ end
+ end
+
+ if platforms.freebsd then
+ defaults.arch = "freebsd-"..target_cpu
+ elseif platforms.dragonfly then
+ defaults.arch = "dragonfly-"..target_cpu
+ elseif platforms.openbsd then
+ defaults.arch = "openbsd-"..target_cpu
+ elseif platforms.netbsd then
+ defaults.arch = "netbsd-"..target_cpu
+ elseif platforms.solaris then
+ defaults.arch = "solaris-"..target_cpu
+ defaults.variables.MAKE = "gmake"
+ end
+
+ -- Expose some more values detected by LuaRocks for use by rockspec authors.
+ defaults.variables.LIB_EXTENSION = defaults.lib_extension
+ defaults.variables.OBJ_EXTENSION = defaults.obj_extension
+
+ return defaults
+end
+
+local function use_defaults(cfg, defaults)
+
+ -- Populate variables with values from their 'defaults' counterparts
+ -- if they were not already set by user.
+ if not cfg.variables then
+ cfg.variables = {}
+ end
+ for k,v in pairs(defaults.variables) do
+ if not cfg.variables[k] then
+ cfg.variables[k] = v
+ end
+ end
+
+ util.deep_merge_under(cfg, defaults)
+
+ -- FIXME get rid of this
+ if not cfg.check_certificates then
+ cfg.variables.CURLNOCERTFLAG = "-k"
+ cfg.variables.WGETNOCERTFLAG = "--no-check-certificate"
+ end
+end
+
+--------------------------------------------------------------------------------
+
+local cfg = {}
+
+--- Initializes the LuaRocks configuration for variables, paths
+-- and OS detection.
+-- @param detected table containing information detected about the
+-- environment. All fields below are optional:
+-- * lua_version (in x.y format, e.g. "5.3")
+-- * lua_bindir (e.g. "/usr/local/bin")
+-- * lua_dir (e.g. "/usr/local")
+-- * lua (e.g. "/usr/local/bin/lua-5.3")
+-- * project_dir (a string with the path of the project directory
+-- when using per-project environments, as created with `luarocks init`)
+-- @param warning a logging function for warnings that takes a string
+-- @return true on success; nil and an error message on failure.
+function cfg.init(detected, warning)
+ detected = detected or {}
+
+ local exit_ok = true
+ local exit_err = nil
+ local exit_what = nil
+
+ local hc_ok, hardcoded = pcall(require, "luarocks.core.hardcoded")
+ if not hc_ok then
+ hardcoded = {}
+ end
+
+ local init = cfg.init
+
+ ----------------------------------------
+ -- Reset the cfg table.
+ ----------------------------------------
+
+ for k, _ in pairs(cfg) do
+ cfg[k] = nil
+ end
+
+ cfg.program_version = program_version
+
+ if hardcoded.IS_BINARY then
+ cfg.is_binary = true
+ end
+
+ -- Use detected values as defaults, overridable via config files or CLI args
+
+ local hardcoded_lua = hardcoded.LUA
+ local hardcoded_lua_dir = hardcoded.LUA_DIR
+ local hardcoded_lua_bindir = hardcoded.LUA_BINDIR
+ local hardcoded_lua_incdir = hardcoded.LUA_INCDIR
+ local hardcoded_lua_libdir = hardcoded.LUA_LIBDIR
+ local hardcoded_lua_version = hardcoded.LUA_VERSION or _VERSION:sub(5)
+
+ -- if --lua-version or --lua-dir are passed from the CLI,
+ -- don't use the hardcoded paths at all
+ if detected.given_lua_version or detected.given_lua_dir then
+ hardcoded_lua = nil
+ hardcoded_lua_dir = nil
+ hardcoded_lua_bindir = nil
+ hardcoded_lua_incdir = nil
+ hardcoded_lua_libdir = nil
+ hardcoded_lua_version = nil
+ end
+
+ cfg.lua_version = detected.lua_version or hardcoded_lua_version
+ cfg.project_dir = (not hardcoded.FORCE_CONFIG) and detected.project_dir
+
+ do
+ local lua = detected.lua or hardcoded_lua
+ local lua_dir = detected.lua_dir or hardcoded_lua_dir
+ local lua_bindir = detected.lua_bindir or hardcoded_lua_bindir
+ cfg.variables = {
+ LUA = lua,
+ LUA_DIR = lua_dir,
+ LUA_BINDIR = lua_bindir,
+ LUA_LIBDIR = hardcoded_lua_libdir,
+ LUA_INCDIR = hardcoded_lua_incdir,
+ }
+ end
+
+ cfg.init = init
+
+ ----------------------------------------
+ -- System detection.
+ ----------------------------------------
+
+ -- A proper build of LuaRocks will hardcode the system
+ -- and proc values with hardcoded.SYSTEM and hardcoded.PROCESSOR.
+ -- If that is not available, we try to identify the system.
+ local system, processor = sysdetect.detect()
+ if hardcoded.SYSTEM then
+ system = hardcoded.SYSTEM
+ end
+ if hardcoded.PROCESSOR then
+ processor = hardcoded.PROCESSOR
+ end
+
+ if system == "windows" then
+ if os.getenv("VCINSTALLDIR") then
+ -- running from the Development Command prompt for VS 2017
+ system = "windows"
+ else
+ local msystem = os.getenv("MSYSTEM")
+ if msystem == nil then
+ system = "mingw"
+ elseif msystem == "MSYS" then
+ system = "msys"
+ else
+ -- MINGW32 or MINGW64
+ system = "msys2_mingw_w64"
+ end
+ end
+ end
+
+ cfg.target_cpu = processor
+
+ local platforms = make_platforms(system)
+
+ ----------------------------------------
+ -- Platform is determined.
+ -- Let's load the config files.
+ ----------------------------------------
+
+ local sys_config_file
+ local home_config_file
+ local project_config_file
+
+ local config_file_name = "config-"..cfg.lua_version..".lua"
+
+ do
+ local sysconfdir = os.getenv("LUAROCKS_SYSCONFDIR") or hardcoded.SYSCONFDIR
+ if platforms.windows and not platforms.msys2_mingw_w64 then
+ cfg.home = os.getenv("APPDATA") or "c:"
+ cfg.home_tree = dir.path(cfg.home, "luarocks")
+ cfg.sysconfdir = sysconfdir or dir.path((os.getenv("PROGRAMFILES") or "c:"), "luarocks")
+ else
+ cfg.home = os.getenv("HOME") or ""
+ cfg.home_tree = dir.path(cfg.home, ".luarocks")
+ cfg.sysconfdir = sysconfdir or detect_sysconfdir() or "/etc/luarocks"
+ end
+ end
+
+ -- Load system configuration file
+ sys_config_file = dir.path(cfg.sysconfdir, config_file_name)
+ local sys_config_ok, err = load_config_file(cfg, platforms, sys_config_file)
+ if err then
+ exit_ok, exit_err, exit_what = nil, err, "config"
+ end
+
+ -- Load user configuration file (if allowed)
+ local home_config_ok
+ local project_config_ok
+ if not hardcoded.FORCE_CONFIG then
+ local env_var = "LUAROCKS_CONFIG_" .. cfg.lua_version:gsub("%.", "_")
+ local env_value = os.getenv(env_var)
+ if not env_value then
+ env_var = "LUAROCKS_CONFIG"
+ env_value = os.getenv(env_var)
+ end
+ -- first try environment provided file, so we can explicitly warn when it is missing
+ if env_value then
+ local env_ok, err = load_config_file(cfg, platforms, env_value)
+ if err then
+ exit_ok, exit_err, exit_what = nil, err, "config"
+ elseif warning and not env_ok then
+ warning("Warning: could not load configuration file `"..env_value.."` given in environment variable "..env_var.."\n")
+ end
+ if env_ok then
+ home_config_ok = true
+ home_config_file = env_value
+ end
+ end
+
+ -- try XDG config home
+ if platforms.unix and not home_config_ok then
+ local xdg_config_home = os.getenv("XDG_CONFIG_HOME") or dir.path(cfg.home, ".config")
+ cfg.homeconfdir = dir.path(xdg_config_home, "luarocks")
+ home_config_file = dir.path(cfg.homeconfdir, config_file_name)
+ home_config_ok, err = load_config_file(cfg, platforms, home_config_file)
+ if err then
+ exit_ok, exit_err, exit_what = nil, err, "config"
+ end
+ end
+
+ -- try the alternative defaults if there was no environment specified file or it didn't work
+ if not home_config_ok then
+ cfg.homeconfdir = cfg.home_tree
+ home_config_file = dir.path(cfg.homeconfdir, config_file_name)
+ home_config_ok, err = load_config_file(cfg, platforms, home_config_file)
+ if err then
+ exit_ok, exit_err, exit_what = nil, err, "config"
+ end
+ end
+
+ -- finally, use the project-specific config file if any
+ if cfg.project_dir then
+ project_config_file = dir.path(cfg.project_dir, ".luarocks", config_file_name)
+ project_config_ok, err = load_config_file(cfg, platforms, project_config_file)
+ if err then
+ exit_ok, exit_err, exit_what = nil, err, "config"
+ end
+ end
+ end
+
+ -- backwards compatibility:
+ if cfg.lua_interpreter and cfg.variables.LUA_BINDIR and not cfg.variables.LUA then
+ cfg.variables.LUA = dir.path(cfg.variables.LUA_BINDIR, cfg.lua_interpreter)
+ end
+
+ ----------------------------------------
+ -- Config files are loaded.
+ -- Let's finish up the cfg table.
+ ----------------------------------------
+
+ -- Settings given via the CLI (i.e. --lua-dir) take precedence over config files.
+ cfg.project_dir = detected.given_project_dir or cfg.project_dir
+ cfg.lua_version = detected.given_lua_version or cfg.lua_version
+ if detected.given_lua_dir then
+ cfg.variables.LUA = detected.lua
+ cfg.variables.LUA_DIR = detected.given_lua_dir
+ cfg.variables.LUA_BINDIR = detected.lua_bindir
+ cfg.variables.LUA_LIBDIR = nil
+ cfg.variables.LUA_INCDIR = nil
+ end
+
+ -- Build a default list of rocks trees if not given
+ if cfg.rocks_trees == nil then
+ cfg.rocks_trees = {}
+ if cfg.home_tree then
+ table.insert(cfg.rocks_trees, { name = "user", root = cfg.home_tree } )
+ end
+ if hardcoded.PREFIX and hardcoded.PREFIX ~= cfg.home_tree then
+ table.insert(cfg.rocks_trees, { name = "system", root = hardcoded.PREFIX } )
+ end
+ end
+
+ local defaults = make_defaults(cfg.lua_version, processor, platforms, cfg.home)
+
+ if platforms.windows and hardcoded.WIN_TOOLS then
+ local tools = { "SEVENZ", "CP", "FIND", "LS", "MD5SUM", "WGET", }
+ for _, tool in ipairs(tools) do
+ defaults.variables[tool] = '"' .. dir.path(hardcoded.WIN_TOOLS, defaults.variables[tool] .. '.exe') .. '"'
+ end
+ else
+ defaults.fs_use_modules = true
+ end
+
+ -- if only cfg.variables.LUA is given in config files,
+ -- derive LUA_BINDIR and LUA_DIR from them.
+ if cfg.variables.LUA and not cfg.variables.LUA_BINDIR then
+ cfg.variables.LUA_BINDIR = cfg.variables.LUA:match("^(.*)[\\/][^\\/]*$")
+ if not cfg.variables.LUA_DIR then
+ cfg.variables.LUA_DIR = cfg.variables.LUA_BINDIR:gsub("[\\/]bin$", "") or cfg.variables.LUA_BINDIR
+ end
+ end
+
+ use_defaults(cfg, defaults)
+
+ cfg.user_agent = "LuaRocks/"..cfg.program_version.." "..cfg.arch
+
+ cfg.config_files = {
+ project = cfg.project_dir and {
+ file = project_config_file,
+ found = not not project_config_ok,
+ },
+ system = {
+ file = sys_config_file,
+ found = not not sys_config_ok,
+ },
+ user = {
+ file = home_config_file,
+ found = not not home_config_ok,
+ },
+ nearest = project_config_ok
+ and project_config_file
+ or (home_config_ok
+ and home_config_file
+ or sys_config_file),
+ }
+
+ cfg.cache = {}
+
+ ----------------------------------------
+ -- Attributes of cfg are set.
+ -- Let's add some methods.
+ ----------------------------------------
+
+ do
+ local function make_paths_from_tree(tree)
+ local lua_path, lib_path, bin_path
+ if type(tree) == "string" then
+ lua_path = dir.path(tree, cfg.lua_modules_path)
+ lib_path = dir.path(tree, cfg.lib_modules_path)
+ bin_path = dir.path(tree, "bin")
+ else
+ lua_path = tree.lua_dir or dir.path(tree.root, cfg.lua_modules_path)
+ lib_path = tree.lib_dir or dir.path(tree.root, cfg.lib_modules_path)
+ bin_path = tree.bin_dir or dir.path(tree.root, "bin")
+ end
+ return lua_path, lib_path, bin_path
+ end
+
+ function cfg.package_paths(current)
+ local new_path, new_cpath, new_bin = {}, {}, {}
+ local function add_tree_to_paths(tree)
+ local lua_path, lib_path, bin_path = make_paths_from_tree(tree)
+ table.insert(new_path, dir.path(lua_path, "?.lua"))
+ table.insert(new_path, dir.path(lua_path, "?", "init.lua"))
+ table.insert(new_cpath, dir.path(lib_path, "?."..cfg.lib_extension))
+ table.insert(new_bin, bin_path)
+ end
+ if current then
+ add_tree_to_paths(current)
+ end
+ for _,tree in ipairs(cfg.rocks_trees) do
+ add_tree_to_paths(tree)
+ end
+ return table.concat(new_path, ";"), table.concat(new_cpath, ";"), table.concat(new_bin, cfg.export_path_separator)
+ end
+ end
+
+ function cfg.init_package_paths()
+ local lr_path, lr_cpath, lr_bin = cfg.package_paths()
+ package.path = util.cleanup_path(package.path .. ";" .. lr_path, ";", cfg.lua_version, true)
+ package.cpath = util.cleanup_path(package.cpath .. ";" .. lr_cpath, ";", cfg.lua_version, true)
+ end
+
+ --- Check if platform was detected
+ -- @param name string: The platform name to check.
+ -- @return boolean: true if LuaRocks is currently running on queried platform.
+ function cfg.is_platform(name)
+ assert(type(name) == "string")
+ return platforms[name]
+ end
+
+ -- @param direction (optional) "least-specific-first" (default) or "most-specific-first"
+ function cfg.each_platform(direction)
+ direction = direction or "least-specific-first"
+ local i, delta
+ if direction == "least-specific-first" then
+ i = 0
+ delta = 1
+ else
+ i = #platform_order + 1
+ delta = -1
+ end
+ return function()
+ local p
+ repeat
+ i = i + delta
+ p = platform_order[i]
+ until (not p) or platforms[p]
+ return p
+ end
+ end
+
+ function cfg.print_platforms()
+ local platform_keys = {}
+ for k,_ in pairs(platforms) do
+ table.insert(platform_keys, k)
+ end
+ table.sort(platform_keys)
+ return table.concat(platform_keys, ", ")
+ end
+
+ return exit_ok, exit_err, exit_what
+end
+
+return cfg
diff --git a/src/luarocks/core/dir.lua b/src/luarocks/core/dir.lua
new file mode 100644
index 0000000..5d6f2c9
--- /dev/null
+++ b/src/luarocks/core/dir.lua
@@ -0,0 +1,98 @@
+
+local dir = {}
+
+local require = nil
+--------------------------------------------------------------------------------
+
+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
+
+--- Describe a path in a cross-platform way.
+-- Use this function to avoid platform-specific directory
+-- separators in other modules. Removes trailing slashes from
+-- each component given, to avoid repeated separators.
+-- Separators inside strings are kept, to handle URLs containing
+-- protocols.
+-- @param ... strings representing directories
+-- @return string: a string with a platform-specific representation
+-- of the path.
+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
+
+--- Split protocol and path from an URL or local pathname.
+-- URLs should be in the "protocol://path" format.
+-- For local pathnames, "file" is returned as the protocol.
+-- @param url string: an URL or a local pathname.
+-- @return string, string: the protocol, and the pathname without the protocol.
+function dir.split_url(url)
+ assert(type(url) == "string")
+
+ url = unquote(url)
+ local protocol, pathname = url:match("^([^:]*)://(.*)")
+ if not protocol then
+ protocol = "file"
+ pathname = url
+ end
+ return protocol, pathname
+end
+
+--- Normalize a url or local path.
+-- URLs should be in the "protocol://path" format.
+-- Removes trailing and double slashes, and '.' and '..' components.
+-- for 'file' URLs, the native system's slashes are used.
+-- @param url string: an URL or a local pathname.
+-- @return string: Normalized result.
+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
+
+return dir
+
diff --git a/src/luarocks/core/manif.lua b/src/luarocks/core/manif.lua
new file mode 100644
index 0000000..3925f63
--- /dev/null
+++ b/src/luarocks/core/manif.lua
@@ -0,0 +1,114 @@
+
+--- Core functions for querying manifest files.
+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 require = nil
+--------------------------------------------------------------------------------
+
+-- Table with repository identifiers as keys and tables mapping
+-- Lua versions to cached loaded manifests as values.
+local manifest_cache = {}
+
+--- Cache a loaded manifest.
+-- @param repo_url string: The repository identifier.
+-- @param lua_version string: Lua version in "5.x" format, defaults to installed version.
+-- @param manifest table: the manifest to be cached.
+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
+
+--- Attempt to get cached loaded manifest.
+-- @param repo_url string: The repository identifier.
+-- @param lua_version string: Lua version in "5.x" format, defaults to installed version.
+-- @return table or nil: loaded manifest or nil if cache is empty.
+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
+
+--- Back-end function that actually loads the manifest
+-- and stores it in the manifest cache.
+-- @param file string: The local filename of the manifest file.
+-- @param repo_url string: The repository identifier.
+-- @param lua_version string: Lua version in "5.x" format, defaults to installed version.
+-- @return table or (nil, string, string): the manifest or nil,
+-- error message and error code ("open", "load", "run").
+function manif.manifest_loader(file, repo_url, lua_version)
+ local manifest, err, errcode = persist.load_into_table(file)
+ if not manifest 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
+
+--- Load a local manifest describing a repository.
+-- This is used by the luarocks.loader only.
+-- @param repo_url string: URL or pathname for the repository.
+-- @return table or (nil, string, string): A table representing the manifest,
+-- or nil followed by an error message and an error code, see manifest_loader.
+function manif.fast_load_local_manifest(repo_url)
+ assert(type(repo_url) == "string")
+
+ 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, true)
+end
+
+function manif.load_rocks_tree_manifests(deps_mode)
+ local trees = {}
+ path.map_trees(deps_mode, function(tree)
+ local manifest, err = 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 0000000..2f037b4
--- /dev/null
+++ b/src/luarocks/core/path.lua
@@ -0,0 +1,157 @@
+
+--- Core LuaRocks-specific path handling functions.
+local path = {}
+
+local cfg = require("luarocks.core.cfg")
+local dir = require("luarocks.core.dir")
+local require = nil
+
+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
+ assert(type(tree) == "table")
+ return tree.rocks_dir or dir.path(tree.root, cfg.rocks_subdir)
+end
+
+--- Produce a versioned version of a filename.
+-- @param file string: filename (must start with prefix)
+-- @param prefix string: Path prefix for file
+-- @param name string: Rock name
+-- @param version string: Rock version
+-- @return string: a pathname with the same directory parts and a versioned basename.
+function path.versioned_name(file, prefix, name, version)
+ assert(type(file) == "string")
+ assert(type(name) == "string" and not name:match(dir_sep))
+ assert(type(version) == "string")
+
+ local rest = file:sub(#prefix+1):gsub("^" .. dir_sep .. "*", "")
+ local name_version = (name.."_"..version):gsub("%-", "_"):gsub("%.", "_")
+ return dir.path(prefix, name_version.."-"..rest)
+end
+
+--- Convert a pathname to a module identifier.
+-- In Unix, for example, a path "foo/bar/baz.lua" is converted to
+-- "foo.bar.baz"; "bla/init.lua" returns "bla.init"; "foo.so" returns "foo".
+-- @param file string: Pathname of module
+-- @return string: The module identifier, or nil if given path is
+-- not a conformant module path (the function does not check if the
+-- path actually exists).
+function path.path_to_module(file)
+ assert(type(file) == "string")
+
+ 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
+
+ -- remove any beginning and trailing slashes-converted-to-dots
+ 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
+ assert(type(tree) == "table")
+ 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
+ assert(type(tree) == "table")
+ 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 }
+
+--- Return the pathname of the file that would be loaded for a module, indexed.
+-- @param file_name string: module file name as in manifest (eg. "socket/core.so")
+-- @param name string: name of the package (eg. "luasocket")
+-- @param version string: version number (eg. "2.0.2-1")
+-- @param tree string: repository path (eg. "/usr/local")
+-- @param i number: the index, 1 if version is the current default, > 1 otherwise.
+-- This is done this way for use by select_module in luarocks.loader.
+-- @return string: filename of the module (eg. "/usr/local/lib/lua/5.1/socket/core.so")
+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
+ assert(type(tree) == "table")
+ return tree.root
+ end
+end
+
+--- Apply a given function to the active rocks trees based on chosen dependency mode.
+-- @param deps_mode string: Dependency mode: "one" for the current default tree,
+-- "all" for all trees, "order" for all trees with priority >= the current default,
+-- "none" for no trees (this function becomes a nop).
+-- @param fn function: function to be applied, with the tree dir (string) as the first
+-- argument and the remaining varargs of map_trees as the following arguments.
+-- @return a table with all results of invocations of fn collected.
+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 0000000..57e7b5d
--- /dev/null
+++ b/src/luarocks/core/persist.lua
@@ -0,0 +1,81 @@
+
+local persist = {}
+
+local require = nil
+--------------------------------------------------------------------------------
+
+--- Load and run a Lua file in an environment.
+-- @param filename string: the name of the file.
+-- @param env table: the environment table.
+-- @return (true, any) or (nil, string, string): true and the return value
+-- of the file, or nil, an error message and an error code ("open", "load"
+-- or "run") in case of errors.
+function persist.run_file(filename, env)
+ local fd, err = io.open(filename)
+ if not fd then
+ return nil, err, "open"
+ end
+ local str, err = fd:read("*a")
+ fd:close()
+ if not str then
+ return nil, err, "open"
+ end
+ str = str:gsub("^#![^\n]*\n", "")
+ local chunk, ran
+ if _VERSION == "Lua 5.1" then -- Lua 5.1
+ chunk, err = loadstring(str, filename)
+ if chunk then
+ setfenv(chunk, env)
+ ran, err = pcall(chunk)
+ end
+ else -- Lua 5.2
+ chunk, err = load(str, filename, "t", env)
+ if chunk then
+ ran, err = pcall(chunk)
+ end
+ end
+ if not chunk then
+ return nil, "Error loading file: "..err, "load"
+ end
+ if not ran then
+ return nil, "Error running file: "..err, "run"
+ end
+ return true, err
+end
+
+--- Load a Lua file containing assignments, storing them in a table.
+-- The global environment is not propagated to the loaded file.
+-- @param filename string: the name of the file.
+-- @param tbl table or nil: if given, this table is used to store
+-- loaded values.
+-- @return (table, table) or (nil, string, string): a table with the file's assignments
+-- as fields and set of undefined globals accessed in file,
+-- or nil, an error message and an error code ("open"; couldn't open the file,
+-- "load"; compile-time error, or "run"; run-time error)
+-- in case of errors.
+function persist.load_into_table(filename, tbl)
+ assert(type(filename) == "string")
+ assert(type(tbl) == "table" or not tbl)
+
+ local result = tbl or {}
+ local globals = {}
+ local globals_mt = {
+ __index = function(t, 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, 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 0000000..06454f2
--- /dev/null
+++ b/src/luarocks/core/sysdetect.lua
@@ -0,0 +1,419 @@
+-- Detect the operating system and architecture without forking a subprocess.
+--
+-- We are not going for exhaustive list of every historical system here,
+-- but aiming to cover every platform where LuaRocks is known to run.
+-- If your system is not detected, patches are welcome!
+
+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 LITTLE = 1
+-- local BIG = 2
+
+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
+
+--------------------------------------------------------------------------------
+-- @section ELF
+--------------------------------------------------------------------------------
+
+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 = hdr.endian
+ local word = hdr.word
+
+ local strtab_offset
+ local sections = {}
+ 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(sections, section)
+ end
+ if strtab_offset then
+ for _, section in ipairs(sections) 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 = 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)) -- vn_version
+ 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 = 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 == 1 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
+
+--------------------------------------------------------------------------------
+-- @section Mach Objects (Apple)
+--------------------------------------------------------------------------------
+
+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
+ -- fat binary, go for the first one
+ 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
+
+--------------------------------------------------------------------------------
+-- @section PE (Windows)
+--------------------------------------------------------------------------------
+
+local pe_machine = {
+ [0x8664] = "x86_64",
+ [0x01c0] = "arm",
+ [0x01c4] = "armv7l",
+ [0xaa64] = "arm64",
+ [0x014c] = "x86",
+}
+
+local function detect_pe(fd)
+ fd:seek("set", 60) -- position of PE header position
+ local peoffset = read_int32le(fd) -- read position of PE header
+ if not peoffset then
+ return nil
+ end
+ local system = "windows"
+ fd:seek("set", peoffset + 4) -- move to position of Machine section
+ local machine = read(fd, 2, LITTLE)
+ local processor = pe_machine[machine]
+
+ local rdata_pos = fd:read(736):match(".rdata%z%z............(....)")
+ if rdata_pos then
+ rdata_pos = bytes2number(rdata_pos, 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
+
+--------------------------------------------------------------------------------
+-- @section API
+--------------------------------------------------------------------------------
+
+function sysdetect.detect_file(file)
+ assert(type(file) == "string")
+ 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
+ -- Unix
+ files = {
+ "/bin/sh", -- Unix: well-known POSIX path
+ "/proc/self/exe", -- Linux: this should always have a working binary
+ }
+ PATHsep = ":"
+ else
+ -- Windows
+ local systemroot = os.getenv("SystemRoot")
+ files = {
+ systemroot .. "\\system32\\notepad.exe", -- well-known Windows path
+ systemroot .. "\\explorer.exe", -- well-known Windows path
+ }
+ if interp and not interp:lower():match("exe$") then
+ interp = interp .. ".exe"
+ end
+ PATHsep = ";"
+ end
+ if interp then
+ if interp:match(dirsep) then
+ -- interpreter path is absolute
+ 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/util.lua b/src/luarocks/core/util.lua
new file mode 100644
index 0000000..e9abdd3
--- /dev/null
+++ b/src/luarocks/core/util.lua
@@ -0,0 +1,322 @@
+
+local util = {}
+
+local require = nil
+--------------------------------------------------------------------------------
+
+local dir_sep = package.config:sub(1, 1)
+
+--- Run a process and read a its output.
+-- Equivalent to io.popen(cmd):read("*l"), except that it
+-- closes the fd right away.
+-- @param cmd string: The command to execute
+-- @param spec string: "*l" by default, to read a single line.
+-- May be used to read more, passing, for instance, "*a".
+-- @return string: the output of the program.
+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
+
+---
+-- Formats tables with cycles recursively to any depth.
+-- References to other tables are shown as values.
+-- Self references are indicated.
+-- The string returned is "Lua code", which can be processed
+-- (in the case in which indent is composed by spaces or "--").
+-- Userdata and function keys and values are shown as strings,
+-- which logically are exactly not equivalent to the original code.
+-- This routine can serve for pretty formating tables with
+-- proper indentations, apart from printing them:
+-- io.write(table.show(t, "t")) -- a typical use
+-- Written by Julio Manuel Fernandez-Diaz,
+-- Heavily based on "Saving tables with cycles", PIL2, p. 113.
+-- @param t table: is the table.
+-- @param tname string: is the name of the table (optional)
+-- @param top_indent string: is a first indentation (optional).
+-- @return string: the pretty-printed table
+function util.show_table(t, tname, top_indent)
+ local cart -- a container
+ local autoref -- for self references
+
+ 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
+ -- info.name is nil because o is not a calling level
+ if info.what == "C" then
+ return ("%q"):format(so .. ", C function")
+ else
+ -- the information is defined through lines
+ 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 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)
+ -- three spaces between levels
+ add_to_cart(v, fname, indent .. " ", saved, field)
+ end
+ cart = cart .. indent .. "};\n"
+ end
+ end
+ end
+ end
+
+ tname = tname or "__unnamed__"
+ if type(t) ~= "table" then
+ return tname .. " = " .. basic_serialize(t)
+ end
+ cart, autoref = "", ""
+ add_to_cart(t, tname, top_indent)
+ return cart .. autoref
+end
+
+--- Merges contents of src on top of dst's contents
+-- (i.e. if an key from src already exists in dst, replace it).
+-- @param dst Destination table, which will receive src's contents.
+-- @param src Table which provides new contents to dst.
+function util.deep_merge(dst, src)
+ for k, v in pairs(src) do
+ if type(v) == "table" then
+ if dst[k] == nil then
+ dst[k] = {}
+ end
+ if type(dst[k]) == "table" then
+ util.deep_merge(dst[k], v)
+ else
+ dst[k] = v
+ end
+ else
+ dst[k] = v
+ end
+ end
+end
+
+--- Merges contents of src below those of dst's contents
+-- (i.e. if an key from src already exists in dst, do not replace it).
+-- @param dst Destination table, which will receive src's contents.
+-- @param src Table which provides new contents to dst.
+function util.deep_merge_under(dst, src)
+ for k, v in pairs(src) do
+ if type(v) == "table" then
+ if dst[k] == nil then
+ dst[k] = {}
+ end
+ if type(dst[k]) == "table" then
+ util.deep_merge_under(dst[k], v)
+ end
+ elseif dst[k] == nil then
+ dst[k] = v
+ end
+ end
+end
+
+--- Clean up a path-style string ($PATH, $LUA_PATH/package.path, etc.),
+-- removing repeated entries and making sure only the relevant
+-- Lua version is used.
+-- Example: given ("a;b;c;a;b;d", ";"), returns "a;b;c;d".
+-- @param list string: A path string (from $PATH or package.path)
+-- @param sep string: The separator
+-- @param lua_version (optional) string: The Lua version to use.
+-- @param keep_first (optional) if true, keep first occurrence in case
+-- of duplicates; otherwise keep last occurrence. The default is false.
+function util.cleanup_path(list, sep, lua_version, keep_first)
+ assert(type(list) == "string")
+ assert(type(sep) == "string")
+
+ 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
+
+-- from http://lua-users.org/wiki/SplitJoin
+-- by Philippe Lhoste
+function util.split_string(str, delim, maxNb)
+ -- Eliminate bad cases...
+ if string.find(str, delim) == nil then
+ return { str }
+ end
+ if maxNb == nil or maxNb < 1 then
+ maxNb = 0 -- No limit
+ 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 = pos
+ if nb == maxNb then break end
+ end
+ -- Handle the last field
+ if nb ~= maxNb then
+ result[nb + 1] = string.sub(str, lastPos)
+ end
+ return result
+end
+
+--- Return an array of keys of a table.
+-- @param tbl table: The input table.
+-- @return table: The array of keys.
+function util.keys(tbl)
+ local ks = {}
+ for k,_ in pairs(tbl) do
+ table.insert(ks, k)
+ end
+ return ks
+end
+
+--- Print a line to standard error
+function util.printerr(...)
+ io.stderr:write(table.concat({...},"\t"))
+ io.stderr:write("\n")
+end
+
+--- Display a warning message.
+-- @param msg string: the warning message
+function util.warning(msg)
+ util.printerr("Warning: "..msg)
+end
+
+--- Simple sort function used as a default for util.sortedpairs.
+local function default_sort(a, b)
+ local ta = type(a)
+ local tb = type(b)
+ if ta == "number" and tb == "number" then
+ return a < b
+ elseif ta == "number" then
+ return true
+ elseif tb == "number" then
+ return false
+ else
+ return tostring(a) < tostring(b)
+ end
+end
+
+--- A table iterator generator that returns elements sorted by key,
+-- to be used in "for" loops.
+-- @param tbl table: The table to be iterated.
+-- @param sort_function function or table or nil: An optional comparison function
+-- to be used by table.sort when sorting keys, or an array listing an explicit order
+-- for keys. If a value itself is an array, it is taken so that the first element
+-- is a string representing the field name, and the second element is a priority table
+-- for that key, which is returned by the iterator as the third value after the key
+-- and the value.
+-- @return function: the iterator function.
+function util.sortedpairs(tbl, sort_function)
+ sort_function = sort_function or default_sort
+ local keys = util.keys(tbl)
+ local sub_orders = {}
+
+ if type(sort_function) == "function" then
+ table.sort(keys, sort_function)
+ else
+ local order = sort_function
+ local ordered_keys = {}
+ local all_keys = keys
+ keys = {}
+
+ for _, order_entry in ipairs(order) do
+ local key, sub_order
+ if type(order_entry) == "table" then
+ key = order_entry[1]
+ sub_order = order_entry[2]
+ else
+ key = order_entry
+ end
+
+ if tbl[key] then
+ ordered_keys[key] = true
+ sub_orders[key] = sub_order
+ table.insert(keys, key)
+ end
+ end
+
+ table.sort(all_keys, default_sort)
+ for _, key in ipairs(all_keys) do
+ if not ordered_keys[key] then
+ table.insert(keys, key)
+ end
+ end
+ end
+
+ local i = 1
+ return function()
+ local key = keys[i]
+ i = i + 1
+ return key, tbl[key], 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 0000000..8e61798
--- /dev/null
+++ b/src/luarocks/core/vers.lua
@@ -0,0 +1,207 @@
+
+local vers = {}
+
+local util = require("luarocks.core.util")
+local require = nil
+--------------------------------------------------------------------------------
+
+local deltas = {
+ dev = 120000000,
+ scm = 110000000,
+ cvs = 100000000,
+ rc = -1000,
+ pre = -10000,
+ beta = -100000,
+ alpha = -1000000
+}
+
+local version_mt = {
+ --- Equality comparison for versions.
+ -- All version numbers must be equal.
+ -- If both versions have revision numbers, they must be equal;
+ -- otherwise the revision number is ignored.
+ -- @param v1 table: version table to compare.
+ -- @param v2 table: version table to compare.
+ -- @return boolean: true if they are considered equivalent.
+ __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,
+ --- Size comparison for versions.
+ -- All version numbers are compared.
+ -- If both versions have revision numbers, they are compared;
+ -- otherwise the revision number is ignored.
+ -- @param v1 table: version table to compare.
+ -- @param v2 table: version table to compare.
+ -- @return boolean: true if v1 is considered lower than v2.
+ __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,
+ -- @param v1 table: version table to compare.
+ -- @param v2 table: version table to compare.
+ -- @return boolean: true if v1 is considered lower than or equal to v2.
+ __le = function(v1, v2)
+ return not (v2 < v1) -- luacheck: ignore
+ end,
+ --- Return version as a string.
+ -- @param v The version table.
+ -- @return The string representation.
+ __tostring = function(v)
+ return v.string
+ end,
+}
+
+local version_cache = {}
+setmetatable(version_cache, {
+ __mode = "kv"
+})
+
+--- Parse a version string, converting to table format.
+-- A version table contains all components of the version string
+-- converted to numeric format, stored in the array part of the table.
+-- If the version contains a revision, it is stored numerically
+-- in the 'revision' field. The original string representation of
+-- the string is preserved in the 'string' field.
+-- Returned version tables use a metatable
+-- allowing later comparison through relational operators.
+-- @param vstring string: A version number in string format.
+-- @return table or nil: A version table or nil
+-- if the input string contains invalid characters.
+function vers.parse_version(vstring)
+ if not vstring then return nil end
+ assert(type(vstring) == "string")
+
+ 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
+
+ -- trim leading and trailing spaces
+ local v = vstring:match("^%s*(.*)%s*$")
+ version.string = v
+ -- store revision separately if any
+ local main, revision = v:match("(.*)%-(%d+)$")
+ if revision then
+ v = main
+ version.revision = tonumber(revision)
+ end
+ while #v > 0 do
+ -- extract a number
+ local token, rest = v:match("^(%d+)[%.%-%_]*(.*)")
+ if token then
+ add_token(tonumber(token))
+ else
+ -- extract a word
+ 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
+
+--- Utility function to compare version numbers given as strings.
+-- @param a string: one version.
+-- @param b string: another version.
+-- @return boolean: True if a > b.
+function vers.compare_versions(a, b)
+ if a == b then
+ return false
+ end
+ return vers.parse_version(a) > vers.parse_version(b)
+end
+
+--- A more lenient check for equivalence between versions.
+-- This returns true if the requested components of a version
+-- match and ignore the ones that were not given. For example,
+-- when requesting "2", then "2", "2.1", "2.3.5-9"... all match.
+-- When requesting "2.1", then "2.1", "2.1.3" match, but "2.2"
+-- doesn't.
+-- @param version string or table: Version to be tested; may be
+-- in string format or already parsed into a table.
+-- @param requested string or table: Version requested; may be
+-- in string format or already parsed into a table.
+-- @return boolean: True if the tested version matches the requested
+-- version, false otherwise.
+local function partial_match(version, requested)
+ assert(type(version) == "string" or type(version) == "table")
+ assert(type(requested) == "string" or type(version) == "table")
+
+ if type(version) ~= "table" then version = vers.parse_version(version) end
+ if type(requested) ~= "table" then requested = vers.parse_version(requested) end
+ if not version or not requested 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
+
+--- Check if a version satisfies a set of constraints.
+-- @param version table: A version in table format
+-- @param constraints table: An array of constraints in table format.
+-- @return boolean: True if version satisfies all constraints,
+-- false otherwise.
+function vers.match_constraints(version, constraints)
+ assert(type(version) == "table")
+ assert(type(constraints) == "table")
+ local ok = true
+ setmetatable(version, version_mt)
+ for _, constr in pairs(constraints) do
+ if type(constr.version) == "string" then
+ constr.version = vers.parse_version(constr.version)
+ end
+ local constr_version, constr_op = constr.version, constr.op
+ setmetatable(constr_version, version_mt)
+ if constr_op == "==" then ok = version == constr_version
+ elseif constr_op == "~=" then ok = version ~= constr_version
+ elseif constr_op == ">" then ok = version > constr_version
+ elseif constr_op == "<" then ok = version < constr_version
+ elseif constr_op == ">=" then ok = version >= constr_version
+ elseif constr_op == "<=" then ok = version <= constr_version
+ elseif constr_op == "~>" then ok = partial_match(version, constr_version)
+ end
+ if not ok then break end
+ end
+ return ok
+end
+
+return vers