summaryrefslogtreecommitdiff
path: root/src/luarocks/build
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/build
fetch tarballHEADmaster
Diffstat (limited to 'src/luarocks/build')
-rw-r--r--src/luarocks/build/builtin.lua395
-rw-r--r--src/luarocks/build/cmake.lua78
-rw-r--r--src/luarocks/build/command.lua41
-rw-r--r--src/luarocks/build/make.lua98
4 files changed, 612 insertions, 0 deletions
diff --git a/src/luarocks/build/builtin.lua b/src/luarocks/build/builtin.lua
new file mode 100644
index 0000000..4c15d2b
--- /dev/null
+++ b/src/luarocks/build/builtin.lua
@@ -0,0 +1,395 @@
+
+--- A builtin build system: back-end to provide a portable way of building C-based Lua modules.
+local builtin = {}
+
+-- This build driver checks LUA_INCDIR and LUA_LIBDIR on demand,
+-- so that pure-Lua rocks don't need to have development headers
+-- installed.
+builtin.skip_lua_inc_lib_check = true
+
+local unpack = unpack or table.unpack
+local dir_sep = package.config:sub(1, 1)
+
+local fs = require("luarocks.fs")
+local path = require("luarocks.path")
+local util = require("luarocks.util")
+local cfg = require("luarocks.core.cfg")
+local dir = require("luarocks.dir")
+local deps = require("luarocks.deps")
+
+local function autoextract_libs(external_dependencies, variables)
+ if not external_dependencies then
+ return nil, nil, nil
+ end
+ local libs = {}
+ local incdirs = {}
+ local libdirs = {}
+ for name, data in pairs(external_dependencies) do
+ if data.library then
+ table.insert(libs, data.library)
+ table.insert(incdirs, variables[name .. "_INCDIR"])
+ table.insert(libdirs, variables[name .. "_LIBDIR"])
+ end
+ end
+ return libs, incdirs, libdirs
+end
+
+do
+ local function get_cmod_name(file)
+ local fd = io.open(dir.path(fs.current_dir(), file), "r")
+ if not fd then return nil end
+ local data = fd:read("*a")
+ fd:close()
+ return (data:match("int%s+luaopen_([a-zA-Z0-9_]+)"))
+ end
+
+ local skiplist = {
+ ["spec"] = true,
+ [".luarocks"] = true,
+ ["lua_modules"] = true,
+ ["test.lua"] = true,
+ ["tests.lua"] = true,
+ }
+
+ function builtin.autodetect_modules(libs, incdirs, libdirs)
+ local modules = {}
+ local install
+ local copy_directories
+
+ local prefix = ""
+ for _, parent in ipairs({"src", "lua", "lib"}) do
+ if fs.is_dir(parent) then
+ fs.change_dir(parent)
+ prefix = parent .. dir_sep
+ break
+ end
+ end
+
+ for _, file in ipairs(fs.find()) do
+ local base = file:match("^([^\\/]*)")
+ if not skiplist[base] then
+ local luamod = file:match("(.*)%.lua$")
+ if luamod then
+ modules[path.path_to_module(file)] = prefix .. file
+ else
+ local cmod = file:match("(.*)%.c$")
+ if cmod then
+ local modname = get_cmod_name(file) or path.path_to_module(file:gsub("%.c$", ".lua"))
+ modules[modname] = {
+ sources = prefix..file,
+ libraries = libs,
+ incdirs = incdirs,
+ libdirs = libdirs,
+ }
+ end
+ end
+ end
+ end
+
+ if prefix ~= "" then
+ fs.pop_dir()
+ end
+
+ local bindir = (fs.is_dir(dir.path("src", "bin")) and dir.path("src", "bin"))
+ or (fs.is_dir("bin") and "bin")
+ if bindir then
+ install = { bin = {} }
+ for _, file in ipairs(fs.list_dir(bindir)) do
+ table.insert(install.bin, dir.path(bindir, file))
+ end
+ end
+
+ for _, directory in ipairs({ "doc", "docs", "samples", "tests" }) do
+ if fs.is_dir(directory) then
+ if not copy_directories then
+ copy_directories = {}
+ end
+ table.insert(copy_directories, directory)
+ end
+ end
+
+ return modules, install, copy_directories
+ end
+end
+
+--- Run a command displaying its execution on standard output.
+-- @return boolean: true if command succeeds (status code 0), false
+-- otherwise.
+local function execute(...)
+ io.stdout:write(table.concat({...}, " ").."\n")
+ return fs.execute(...)
+end
+
+--- Driver function for the builtin build back-end.
+-- @param rockspec table: the loaded rockspec.
+-- @return boolean or (nil, string): true if no errors occurred,
+-- nil and an error message otherwise.
+function builtin.run(rockspec, no_install)
+ assert(rockspec:type() == "rockspec")
+ local compile_object, compile_library, compile_static_library
+
+ local build = rockspec.build
+ local variables = rockspec.variables
+ local checked_lua_h = false
+
+ for _, var in ipairs{ "CC", "CFLAGS", "LDFLAGS" } do
+ variables[var] = variables[var] or os.getenv(var) or ""
+ end
+
+ local function add_flags(extras, flag, flags)
+ if flags then
+ if type(flags) ~= "table" then
+ flags = { tostring(flags) }
+ end
+ util.variable_substitutions(flags, variables)
+ for _, v in ipairs(flags) do
+ table.insert(extras, flag:format(v))
+ end
+ end
+ end
+
+ if cfg.is_platform("mingw32") then
+ compile_object = function(object, source, defines, incdirs)
+ local extras = {}
+ add_flags(extras, "-D%s", defines)
+ add_flags(extras, "-I%s", incdirs)
+ return execute(variables.CC.." "..variables.CFLAGS, "-c", "-o", object, "-I"..variables.LUA_INCDIR, source, unpack(extras))
+ end
+ compile_library = function(library, objects, libraries, libdirs, name)
+ local extras = { unpack(objects) }
+ add_flags(extras, "-L%s", libdirs)
+ add_flags(extras, "-l%s", libraries)
+ extras[#extras+1] = dir.path(variables.LUA_LIBDIR, variables.LUALIB)
+
+ if variables.CC == "clang" or variables.CC == "clang-cl" then
+ local exported_name = name:gsub("%.", "_")
+ exported_name = exported_name:match('^[^%-]+%-(.+)$') or exported_name
+ extras[#extras+1] = string.format("-Wl,-export:luaopen_%s", exported_name)
+ else
+ extras[#extras+1] = "-l" .. (variables.MSVCRT or "m")
+ end
+
+ local ok = execute(variables.LD.." "..variables.LDFLAGS.." "..variables.LIBFLAG, "-o", library, unpack(extras))
+ return ok
+ end
+ --[[ TODO disable static libs until we fix the conflict in the manifest, which will take extending the manifest format.
+ compile_static_library = function(library, objects, libraries, libdirs, name)
+ local ok = execute(variables.AR, "rc", library, unpack(objects))
+ if ok then
+ ok = execute(variables.RANLIB, library)
+ end
+ return ok
+ end
+ ]]
+ elseif cfg.is_platform("win32") then
+ compile_object = function(object, source, defines, incdirs)
+ local extras = {}
+ add_flags(extras, "-D%s", defines)
+ add_flags(extras, "-I%s", incdirs)
+ return execute(variables.CC.." "..variables.CFLAGS, "-c", "-Fo"..object, "-I"..variables.LUA_INCDIR, source, unpack(extras))
+ end
+ compile_library = function(library, objects, libraries, libdirs, name)
+ local extras = { unpack(objects) }
+ add_flags(extras, "-libpath:%s", libdirs)
+ add_flags(extras, "%s.lib", libraries)
+ local basename = dir.base_name(library):gsub(".[^.]*$", "")
+ local deffile = basename .. ".def"
+ local def = io.open(dir.path(fs.current_dir(), deffile), "w+")
+ local exported_name = name:gsub("%.", "_")
+ exported_name = exported_name:match('^[^%-]+%-(.+)$') or exported_name
+ def:write("EXPORTS\n")
+ def:write("luaopen_"..exported_name.."\n")
+ def:close()
+ local ok = execute(variables.LD, "-dll", "-def:"..deffile, "-out:"..library, dir.path(variables.LUA_LIBDIR, variables.LUALIB), unpack(extras))
+ local basedir = ""
+ if name:find("%.") ~= nil then
+ basedir = name:gsub("%.%w+$", "\\")
+ basedir = basedir:gsub("%.", "\\")
+ end
+ local manifestfile = basedir .. basename..".dll.manifest"
+
+ if ok and fs.exists(manifestfile) then
+ ok = execute(variables.MT, "-manifest", manifestfile, "-outputresource:"..basedir..basename..".dll;2")
+ end
+ return ok
+ end
+ --[[ TODO disable static libs until we fix the conflict in the manifest, which will take extending the manifest format.
+ compile_static_library = function(library, objects, libraries, libdirs, name)
+ local ok = execute(variables.AR, "-out:"..library, unpack(objects))
+ return ok
+ end
+ ]]
+ else
+ compile_object = function(object, source, defines, incdirs)
+ local extras = {}
+ add_flags(extras, "-D%s", defines)
+ add_flags(extras, "-I%s", incdirs)
+ return execute(variables.CC.." "..variables.CFLAGS, "-I"..variables.LUA_INCDIR, "-c", source, "-o", object, unpack(extras))
+ end
+ compile_library = function (library, objects, libraries, libdirs)
+ local extras = { unpack(objects) }
+ add_flags(extras, "-L%s", libdirs)
+ if cfg.gcc_rpath then
+ add_flags(extras, "-Wl,-rpath,%s", libdirs)
+ end
+ add_flags(extras, "-l%s", libraries)
+ if cfg.link_lua_explicitly then
+ extras[#extras+1] = "-L"..variables.LUA_LIBDIR
+ extras[#extras+1] = "-llua"
+ end
+ return execute(variables.LD.." "..variables.LDFLAGS.." "..variables.LIBFLAG, "-o", library, unpack(extras))
+ end
+ compile_static_library = function(library, objects, libraries, libdirs, name) -- luacheck: ignore 211
+ local ok = execute(variables.AR, "rc", library, unpack(objects))
+ if ok then
+ ok = execute(variables.RANLIB, library)
+ end
+ return ok
+ end
+ end
+
+ local ok, err
+ local lua_modules = {}
+ local lib_modules = {}
+ local luadir = path.lua_dir(rockspec.name, rockspec.version)
+ local libdir = path.lib_dir(rockspec.name, rockspec.version)
+
+ local autolibs, autoincdirs, autolibdirs = autoextract_libs(rockspec.external_dependencies, rockspec.variables)
+
+ if not build.modules then
+ if rockspec:format_is_at_least("3.0") then
+ local install, copy_directories
+ build.modules, install, copy_directories = builtin.autodetect_modules(autolibs, autoincdirs, autolibdirs)
+ build.install = build.install or install
+ build.copy_directories = build.copy_directories or copy_directories
+ else
+ return nil, "Missing build.modules table"
+ end
+ end
+
+ local compile_temp_dir
+
+ local mkdir_cache = {}
+ local function cached_make_dir(name)
+ if name == "" or mkdir_cache[name] then
+ return true
+ end
+ mkdir_cache[name] = true
+ return fs.make_dir(name)
+ end
+
+ for name, info in pairs(build.modules) do
+ local moddir = path.module_to_path(name)
+ if type(info) == "string" then
+ local ext = info:match("%.([^.]+)$")
+ if ext == "lua" then
+ local filename = dir.base_name(info)
+ if filename == "init.lua" and not name:match("%.init$") then
+ moddir = path.module_to_path(name..".init")
+ else
+ local basename = name:match("([^.]+)$")
+ filename = basename..".lua"
+ end
+ local dest = dir.path(luadir, moddir, filename)
+ lua_modules[info] = dest
+ else
+ info = {info}
+ end
+ end
+ if type(info) == "table" then
+ if not checked_lua_h then
+ local ok, err, errcode = deps.check_lua_incdir(rockspec.variables)
+ if not ok then
+ return nil, err, errcode
+ end
+
+ if cfg.link_lua_explicitly then
+ local ok, err, errcode = deps.check_lua_libdir(rockspec.variables)
+ if not ok then
+ return nil, err, errcode
+ end
+ end
+ checked_lua_h = true
+ end
+ local objects = {}
+ local sources = info.sources
+ if info[1] then sources = info end
+ if type(sources) == "string" then sources = {sources} end
+ if type(sources) ~= "table" then
+ return nil, "error in rockspec: module '" .. name .. "' entry has no 'sources' list"
+ end
+ for _, source in ipairs(sources) do
+ if type(source) ~= "string" then
+ return nil, "error in rockspec: module '" .. name .. "' does not specify source correctly."
+ end
+ local object = source:gsub("%.[^.]*$", "."..cfg.obj_extension)
+ if not object then
+ object = source.."."..cfg.obj_extension
+ end
+ ok = compile_object(object, source, info.defines, info.incdirs or autoincdirs)
+ if not ok then
+ return nil, "Failed compiling object "..object
+ end
+ table.insert(objects, object)
+ end
+
+ if not compile_temp_dir then
+ compile_temp_dir = fs.make_temp_dir("build-" .. rockspec.package .. "-" .. rockspec.version)
+ util.schedule_function(fs.delete, compile_temp_dir)
+ end
+
+ local module_name = name:match("([^.]*)$").."."..util.matchquote(cfg.lib_extension)
+ if moddir ~= "" then
+ module_name = dir.path(moddir, module_name)
+ end
+
+ local build_name = dir.path(compile_temp_dir, module_name)
+ local build_dir = dir.dir_name(build_name)
+ cached_make_dir(build_dir)
+
+ lib_modules[build_name] = dir.path(libdir, module_name)
+ ok = compile_library(build_name, objects, info.libraries, info.libdirs or autolibdirs, name)
+ if not ok then
+ return nil, "Failed compiling module "..module_name
+ end
+
+ -- for backwards compatibility, try keeping a copy of the module
+ -- in the old location (luasec-1.3.2-1 rockspec breaks otherwise)
+ if cached_make_dir(dir.dir_name(module_name)) then
+ fs.copy(build_name, module_name)
+ end
+
+ --[[ TODO disable static libs until we fix the conflict in the manifest, which will take extending the manifest format.
+ module_name = name:match("([^.]*)$").."."..util.matchquote(cfg.static_lib_extension)
+ if moddir ~= "" then
+ module_name = dir.path(moddir, module_name)
+ end
+ lib_modules[module_name] = dir.path(libdir, module_name)
+ ok = compile_static_library(module_name, objects, info.libraries, info.libdirs, name)
+ if not ok then
+ return nil, "Failed compiling static library "..module_name
+ end
+ ]]
+ end
+ end
+ if not no_install then
+ for _, mods in ipairs({{ tbl = lua_modules, perms = "read" }, { tbl = lib_modules, perms = "exec" }}) do
+ for name, dest in pairs(mods.tbl) do
+ cached_make_dir(dir.dir_name(dest))
+ ok, err = fs.copy(name, dest, mods.perms)
+ if not ok then
+ return nil, "Failed installing "..name.." in "..dest..": "..err
+ end
+ end
+ end
+ if fs.is_dir("lua") then
+ ok, err = fs.copy_contents("lua", luadir)
+ if not ok then
+ return nil, "Failed copying contents of 'lua' directory: "..err
+ end
+ end
+ end
+ return true
+end
+
+return builtin
diff --git a/src/luarocks/build/cmake.lua b/src/luarocks/build/cmake.lua
new file mode 100644
index 0000000..b7a4786
--- /dev/null
+++ b/src/luarocks/build/cmake.lua
@@ -0,0 +1,78 @@
+
+--- Build back-end for CMake-based modules.
+local cmake = {}
+
+local fs = require("luarocks.fs")
+local util = require("luarocks.util")
+local cfg = require("luarocks.core.cfg")
+
+--- Driver function for the "cmake" build back-end.
+-- @param rockspec table: the loaded rockspec.
+-- @return boolean or (nil, string): true if no errors occurred,
+-- nil and an error message otherwise.
+function cmake.run(rockspec, no_install)
+ assert(rockspec:type() == "rockspec")
+ local build = rockspec.build
+ local variables = build.variables or {}
+
+ -- Pass Env variables
+ variables.CMAKE_MODULE_PATH=os.getenv("CMAKE_MODULE_PATH")
+ variables.CMAKE_LIBRARY_PATH=os.getenv("CMAKE_LIBRARY_PATH")
+ variables.CMAKE_INCLUDE_PATH=os.getenv("CMAKE_INCLUDE_PATH")
+
+ util.variable_substitutions(variables, rockspec.variables)
+
+ local ok, err_msg = fs.is_tool_available(rockspec.variables.CMAKE, "CMake")
+ if not ok then
+ return nil, err_msg
+ end
+
+ -- If inline cmake is present create CMakeLists.txt from it.
+ if type(build.cmake) == "string" then
+ local cmake_handler = assert(io.open(fs.current_dir().."/CMakeLists.txt", "w"))
+ cmake_handler:write(build.cmake)
+ cmake_handler:close()
+ end
+
+ -- Execute cmake with variables.
+ local args = ""
+
+ -- Try to pick the best generator. With msvc and x64, CMake does not select it by default so we need to be explicit.
+ if cfg.cmake_generator then
+ args = args .. ' -G"'..cfg.cmake_generator.. '"'
+ elseif cfg.is_platform("windows") and cfg.target_cpu:match("x86_64$") then
+ args = args .. " -DCMAKE_GENERATOR_PLATFORM=x64"
+ end
+
+ for k,v in pairs(variables) do
+ args = args .. ' -D' ..k.. '="' ..tostring(v).. '"'
+ end
+
+ if not fs.execute_string(rockspec.variables.CMAKE.." -H. -Bbuild.luarocks "..args) then
+ return nil, "Failed cmake."
+ end
+
+ local do_build, do_install
+ if rockspec:format_is_at_least("3.0") then
+ do_build = (build.build_pass == nil) and true or build.build_pass
+ do_install = (build.install_pass == nil) and true or build.install_pass
+ else
+ do_build = true
+ do_install = true
+ end
+
+ if do_build then
+ if not fs.execute_string(rockspec.variables.CMAKE.." --build build.luarocks --config Release") then
+ return nil, "Failed building."
+ end
+ end
+ if do_install and not no_install then
+ if not fs.execute_string(rockspec.variables.CMAKE.." --build build.luarocks --target install --config Release") then
+ return nil, "Failed installing."
+ end
+ end
+
+ return true
+end
+
+return cmake
diff --git a/src/luarocks/build/command.lua b/src/luarocks/build/command.lua
new file mode 100644
index 0000000..b0c4aa7
--- /dev/null
+++ b/src/luarocks/build/command.lua
@@ -0,0 +1,41 @@
+
+--- Build back-end for raw listing of commands in rockspec files.
+local command = {}
+
+local fs = require("luarocks.fs")
+local util = require("luarocks.util")
+local cfg = require("luarocks.core.cfg")
+
+--- Driver function for the "command" build back-end.
+-- @param rockspec table: the loaded rockspec.
+-- @return boolean or (nil, string): true if no errors occurred,
+-- nil and an error message otherwise.
+function command.run(rockspec, not_install)
+ assert(rockspec:type() == "rockspec")
+
+ local build = rockspec.build
+
+ util.variable_substitutions(build, rockspec.variables)
+
+ local env = {
+ CC = cfg.variables.CC,
+ --LD = cfg.variables.LD,
+ --CFLAGS = cfg.variables.CFLAGS,
+ }
+
+ if build.build_command then
+ util.printout(build.build_command)
+ if not fs.execute_env(env, build.build_command) then
+ return nil, "Failed building."
+ end
+ end
+ if build.install_command and not not_install then
+ util.printout(build.install_command)
+ if not fs.execute_env(env, build.install_command) then
+ return nil, "Failed installing."
+ end
+ end
+ return true
+end
+
+return command
diff --git a/src/luarocks/build/make.lua b/src/luarocks/build/make.lua
new file mode 100644
index 0000000..4345ddf
--- /dev/null
+++ b/src/luarocks/build/make.lua
@@ -0,0 +1,98 @@
+
+--- Build back-end for using Makefile-based packages.
+local make = {}
+
+local unpack = unpack or table.unpack
+
+local fs = require("luarocks.fs")
+local util = require("luarocks.util")
+local cfg = require("luarocks.core.cfg")
+
+--- Call "make" with given target and variables
+-- @param make_cmd string: the make command to be used (typically
+-- configured through variables.MAKE in the config files, or
+-- the appropriate platform-specific default).
+-- @param pass boolean: If true, run make; if false, do nothing.
+-- @param target string: The make target; an empty string indicates
+-- the default target.
+-- @param variables table: A table containing string-string key-value
+-- pairs representing variable assignments to be passed to make.
+-- @return boolean: false if any errors occurred, true otherwise.
+local function make_pass(make_cmd, pass, target, variables)
+ assert(type(pass) == "boolean")
+ assert(type(target) == "string")
+ assert(type(variables) == "table")
+
+ local assignments = {}
+ for k,v in pairs(variables) do
+ table.insert(assignments, k.."="..v)
+ end
+ if pass then
+ return fs.execute(make_cmd.." "..target, unpack(assignments))
+ else
+ return true
+ end
+end
+
+--- Driver function for the "make" build back-end.
+-- @param rockspec table: the loaded rockspec.
+-- @return boolean or (nil, string): true if no errors occurred,
+-- nil and an error message otherwise.
+function make.run(rockspec, not_install)
+ assert(rockspec:type() == "rockspec")
+
+ local build = rockspec.build
+
+ if build.build_pass == nil then build.build_pass = true end
+ if build.install_pass == nil then build.install_pass = true end
+ build.build_variables = build.build_variables or {}
+ build.install_variables = build.install_variables or {}
+ build.build_target = build.build_target or ""
+ build.install_target = build.install_target or "install"
+ local makefile = build.makefile or cfg.makefile
+ if makefile then
+ -- Assumes all make's accept -f. True for POSIX make, GNU make and Microsoft nmake.
+ build.build_target = "-f "..makefile.." "..build.build_target
+ build.install_target = "-f "..makefile.." "..build.install_target
+ end
+
+ if build.variables then
+ for var, val in pairs(build.variables) do
+ build.build_variables[var] = val
+ build.install_variables[var] = val
+ end
+ end
+
+ util.warn_if_not_used(build.build_variables, { CFLAGS=true }, "variable %s was not passed in build_variables")
+
+ util.variable_substitutions(build.build_variables, rockspec.variables)
+ util.variable_substitutions(build.install_variables, rockspec.variables)
+
+ local auto_variables = { "CC" }
+
+ for _, variable in pairs(auto_variables) do
+ if not build.build_variables[variable] then
+ build.build_variables[variable] = rockspec.variables[variable]
+ end
+ if not build.install_variables[variable] then
+ build.install_variables[variable] = rockspec.variables[variable]
+ end
+ end
+
+ -- backwards compatibility
+ local make_cmd = cfg.make or rockspec.variables.MAKE
+
+ local ok = make_pass(make_cmd, build.build_pass, build.build_target, build.build_variables)
+ if not ok then
+ return nil, "Failed building."
+ end
+ if not not_install then
+ ok = make_pass(make_cmd, build.install_pass, build.install_target, build.install_variables)
+ if not ok then
+ return nil, "Failed installing."
+ end
+ end
+ return true
+end
+
+return make