diff options
| author | Mike Vink <mike@pionative.com> | 2025-02-03 21:29:42 +0100 |
|---|---|---|
| committer | Mike Vink <mike@pionative.com> | 2025-02-03 21:29:42 +0100 |
| commit | 5155816b7b925dec5d5feb1568b1d7ceb00938b9 (patch) | |
| tree | deca28ea15e79f6f804c3d90d2ba757881638af5 /src/luarocks/core | |
Diffstat (limited to 'src/luarocks/core')
| -rw-r--r-- | src/luarocks/core/cfg.lua | 940 | ||||
| -rw-r--r-- | src/luarocks/core/dir.lua | 98 | ||||
| -rw-r--r-- | src/luarocks/core/manif.lua | 114 | ||||
| -rw-r--r-- | src/luarocks/core/path.lua | 157 | ||||
| -rw-r--r-- | src/luarocks/core/persist.lua | 81 | ||||
| -rw-r--r-- | src/luarocks/core/sysdetect.lua | 419 | ||||
| -rw-r--r-- | src/luarocks/core/util.lua | 322 | ||||
| -rw-r--r-- | src/luarocks/core/vers.lua | 207 |
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 |
