--- A builtin build system: back-end to provide a portable way of building C-based Lua modules. --module("luarocks.build.builtin", package.seeall) local builtin = {} local unpack = unpack or table.unpack local fs = require("luarocks.fs") local path = require("luarocks.path") local util = require("luarocks.util") local cfg = require("luarocks.cfg") local dir = require("luarocks.dir") --- 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 --- Makes an RC file with an embedded Lua script, for building .exes on Windows -- @return nil if could open files, error otherwise local function make_rc(luafilename, rcfilename) --TODO EXEWRAPPER local rcfile = io.open(rcfilename, "w") if not rcfile then error("Could not open "..rcfilename.." for writing.") end rcfile:write("STRINGTABLE\r\nBEGIN\r\n") local i = 1 for line in io.lines(luafilename) do if not line:match("^#!") then rcfile:write(i .. " \"") line = line:gsub("\\", "\\\\"):gsub('"', '""'):gsub("[\r\n]+", "") rcfile:write(line .. "\\r\\n\"\r\n") i = i + 1 end end rcfile:write("END\r\n") rcfile:close() end --- Driver function for the builtin build back-end. -- @param rockspec table: the loaded rockspec. -- @return boolean or (nil, string): true if no errors ocurred, -- nil and an error message otherwise. function builtin.run(rockspec) assert(type(rockspec) == "table") local compile_object, compile_library, compile_wrapper_binary --TODO EXEWRAPPER local build = rockspec.build local variables = rockspec.variables 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) 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) extras[#extras+1] = "-l" .. (variables.MSVCRT or "m") local ok = execute(variables.LD.." "..variables.LIBFLAG, "-o", library, unpack(extras)) return ok end compile_wrapper_binary = function(fullname, name) --TODO EXEWRAPPER local fullbasename = fullname:gsub("%.lua$", ""):gsub("/", "\\") local basename = name:gsub("%.lua$", ""):gsub("/", "\\") local rcname = basename..".rc" local resname = basename..".o" local wrapname = basename..".exe" make_rc(fullname, fullbasename..".rc") local ok = execute(variables.RC, "-o", resname, rcname) if not ok then return ok end ok = execute(variables.CC.." "..variables.CFLAGS, "-I"..variables.LUA_INCDIR, "-o", wrapname, resname, variables.WRAPPER, dir.path(variables.LUA_LIBDIR, variables.LUALIB), "-l" .. (variables.MSVCRT or "m"), "-luser32") return ok, wrapname 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+") def:write("EXPORTS\n") def:write("luaopen_"..name:gsub("%.", "_").."\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 compile_wrapper_binary = function(fullname, name) --TODO EXEWRAPPER local fullbasename = fullname:gsub("%.lua$", ""):gsub("/", "\\") local basename = name:gsub("%.lua$", ""):gsub("/", "\\") local object = basename..".obj" local rcname = basename..".rc" local resname = basename..".res" local wrapname = basename..".exe" make_rc(fullname, fullbasename..".rc") local ok = execute(variables.RC, "-nologo", "-r", "-fo"..resname, rcname) if not ok then return ok end ok = execute(variables.CC.." "..variables.CFLAGS, "-c", "-Fo"..object, "-I"..variables.LUA_INCDIR, variables.WRAPPER) if not ok then return ok end ok = execute(variables.LD, "-out:"..wrapname, resname, object, dir.path(variables.LUA_LIBDIR, variables.LUALIB), "user32.lib") local manifestfile = wrapname..".manifest" if ok and fs.exists(manifestfile) then ok = execute(variables.MT, "-manifest", manifestfile, "-outputresource:"..wrapname..";1") end return ok, wrapname 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.is_platform("cygwin") then add_flags(extras, "-l%s", {"lua"}) end return execute(variables.LD.." "..variables.LIBFLAG, "-o", library, "-L"..variables.LUA_LIBDIR, unpack(extras)) end compile_wrapper_binary = function(_, name) return true, name end --TODO EXEWRAPPER end local ok = true local built_modules = {} local luadir = path.lua_dir(rockspec.name, rockspec.version) local libdir = path.lib_dir(rockspec.name, rockspec.version) --TODO EXEWRAPPER -- On Windows, compiles an .exe for each Lua file in build.install.bin, and -- replaces the filename with the .exe name. Strips the .lua extension if it exists, -- otherwise just appends .exe to the name. Only if `cfg.exewrapper = true` if build.install and build.install.bin then for key, name in pairs(build.install.bin) do local fullname = dir.path(fs.current_dir(), name) if cfg.exewrapper and fs.is_lua(fullname) then ok, name = compile_wrapper_binary(fullname, name) if ok then build.install.bin[key] = name else return nil, "Build error in wrapper binaries" end end end end if not build.modules then return nil, "Missing build.modules table" 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 info:match("init%.lua$") and not name:match("%.init$") then moddir = path.module_to_path(name..".init") else local basename = name:match("([^.]+)$") local baseinfo = filename:gsub("%.lua$", "") if basename ~= baseinfo then filename = basename..".lua" end end local dest = dir.path(luadir, moddir, filename) built_modules[info] = dest else info = {info} end end if type(info) == "table" then local objects = {} local sources = info.sources if info[1] then sources = info end if type(sources) == "string" then sources = {sources} end for _, source in ipairs(sources) do 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) if not ok then return nil, "Failed compiling object "..object end table.insert(objects, object) end if not ok then break end local module_name = name:match("([^.]*)$").."."..util.matchquote(cfg.lib_extension) if moddir ~= "" then module_name = dir.path(moddir, module_name) local ok, err = fs.make_dir(moddir) if not ok then return nil, err end end built_modules[module_name] = dir.path(libdir, module_name) ok = compile_library(module_name, objects, info.libraries, info.libdirs, name) if not ok then return nil, "Failed compiling module "..module_name end end end for name, dest in pairs(built_modules) do fs.make_dir(dir.dir_name(dest)) ok = fs.copy(name, dest) if not ok then return nil, "Failed installing "..name.." in "..dest end end if fs.is_dir("lua") then local ok, err = fs.copy_contents("lua", luadir) if not ok then return nil, "Failed copying contents of 'lua' directory: "..err end end return true end return builtin