--!A cross-platform build utility based on Lua -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. -- You may obtain a copy of the License at -- -- http://www.apache.org/licenses/LICENSE-2.0 -- -- Unless required by applicable law or agreed to in writing, software -- distributed under the License is distributed on an "AS IS" BASIS, -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -- See the License for the specific language governing permissions and -- limitations under the License. -- -- Copyright (C) 2015-present, TBOOX Open Source Group. -- -- @author ruki -- @file meson.lua -- -- imports import("core.base.option") import("core.project.config") import("core.tool.toolchain") import("core.tool.linker") import("core.tool.compiler") import("lib.detect.find_tool") import("private.utils.executable_path") -- get build directory function _get_buildir(package, opt) if opt and opt.buildir then return opt.buildir else _g.buildir = _g.buildir or package:buildir() return _g.buildir end end -- map compiler flags function _map_compflags(package, langkind, name, values) return compiler.map_flags(langkind, name, values, {target = package}) end -- map linker flags function _map_linkflags(package, targetkind, sourcekinds, name, values) return linker.map_flags(targetkind, sourcekinds, name, values, {target = package}) end -- get pkg-config, we need force to find it, because package install environments will be changed function _get_pkgconfig(package) if package:is_plat("windows") then local pkgconf = find_tool("pkgconf", {force = true}) if pkgconf then return pkgconf.program end end local pkgconfig = find_tool("pkg-config", {force = true}) if pkgconfig then return pkgconfig.program end end -- translate flags function _translate_flags(package, flags) if package:is_plat("android") then local flags_new = {} for _, flag in ipairs(flags) do if flag:startswith("-gcc-toolchain ") or flag:startswith("-target ") or flag:startswith("-isystem ") then table.join2(flags_new, flag:split(" ", {limit = 2})) else table.insert(flags_new, flag) end end flags = flags_new end return flags end -- get cross file function _get_cross_file(package, opt) opt = opt or {} local crossfile = path.join(_get_buildir(package, opt), "cross_file.txt") if not os.isfile(crossfile) then local file = io.open(crossfile, "w") -- binaries file:print("[binaries]") local cc = package:build_getenv("cc") if cc then file:print("c=['%s']", executable_path(cc)) end local cxx = package:build_getenv("cxx") if cxx then file:print("cpp=['%s']", executable_path(cxx)) end local ld = package:build_getenv("ld") if ld then file:print("ld=['%s']", executable_path(ld)) end -- we cannot pass link.exe to ar for msvc, it will raise `unknown linker` if not package:is_plat("windows") then local ar = package:build_getenv("ar") if ar then file:print("ar=['%s']", executable_path(ar)) end end local strip = package:build_getenv("strip") if strip then file:print("strip=['%s']", executable_path(strip)) end local ranlib = package:build_getenv("ranlib") if ranlib then file:print("ranlib=['%s']", executable_path(ranlib)) end if package:is_plat("mingw") then local mrc = package:build_getenv("mrc") if mrc then file:print("windres=['%s']", executable_path(mrc)) end end local cmake = find_tool("cmake") if cmake then file:print("cmake=['%s']", executable_path(cmake.program)) end local pkgconfig = _get_pkgconfig(package) if pkgconfig then file:print("pkgconfig=['%s']", executable_path(pkgconfig)) end file:print("") -- built-in options file:print("[built-in options]") local cflags = table.join(table.wrap(package:build_getenv("cxflags")), package:build_getenv("cflags")) local cxxflags = table.join(table.wrap(package:build_getenv("cxflags")), package:build_getenv("cxxflags")) local asflags = table.wrap(package:build_getenv("asflags")) local arflags = table.wrap(package:build_getenv("arflags")) local ldflags = table.wrap(package:build_getenv("ldflags")) local shflags = table.wrap(package:build_getenv("shflags")) table.join2(cflags, opt.cflags) table.join2(cflags, opt.cxflags) table.join2(cxxflags, opt.cxxflags) table.join2(cxxflags, opt.cxflags) table.join2(asflags, opt.asflags) table.join2(ldflags, opt.ldflags) table.join2(shflags, opt.shflags) table.join2(cflags, _get_cflags_from_packagedeps(package, opt)) table.join2(cxxflags, _get_cflags_from_packagedeps(package, opt)) table.join2(ldflags, _get_ldflags_from_packagedeps(package, opt)) table.join2(shflags, _get_ldflags_from_packagedeps(package, opt)) if #cflags > 0 then cflags = _translate_flags(package, cflags) file:print("c_args=['%s']", table.concat(cflags, "', '")) end if #cxxflags > 0 then cxxflags = _translate_flags(package, cxxflags) file:print("cpp_args=['%s']", table.concat(cxxflags, "', '")) end local linkflags = table.join(ldflags or {}, shflags) if #linkflags > 0 then linkflags = _translate_flags(package, linkflags) file:print("c_link_args=['%s']", table.concat(linkflags, "', '")) file:print("cpp_link_args=['%s']", table.concat(linkflags, "', '")) end file:print("") -- host machine file:print("[host_machine]") if opt.host_machine then file:print("%s", opt.host_machine) elseif package:is_plat("iphoneos", "macosx") then local cpu local cpu_family if package:is_arch("arm64") then cpu = "aarch64" cpu_family = "aarch64" elseif package:is_arch("armv7") then cpu = "arm" cpu_family = "arm" elseif package:is_arch("x64", "x86_64") then cpu = "x86_64" cpu_family = "x86_64" elseif package:is_arch("x86", "i386") then cpu = "i686" cpu_family = "x86" else raise("unsupported arch(%s)", package:arch()) end file:print("system = 'darwin'") file:print("cpu_family = '%s'", cpu_family) file:print("cpu = '%s'", cpu) file:print("endian = 'little'") elseif package:is_plat("android") then local cpu local cpu_family if package:is_arch("arm64-v8a") then cpu = "aarch64" cpu_family = "aarch64" elseif package:is_arch("armeabi-v7a") then cpu = "arm" cpu_family = "arm" elseif package:is_arch("x64", "x86_64") then cpu = "x86_64" cpu_family = "x86_64" elseif package:is_arch("x86", "i386") then cpu = "i686" cpu_family = "x86" else raise("unsupported arch(%s)", package:arch()) end file:print("system = 'android'") file:print("cpu_family = '%s'", cpu_family) file:print("cpu = '%s'", cpu) file:print("endian = 'little'") elseif package:is_plat("mingw") then local cpu local cpu_family if package:is_arch("x64", "x86_64") then cpu = "x86_64" cpu_family = "x86_64" elseif package:is_arch("x86", "i386") then cpu = "i686" cpu_family = "x86" else raise("unsupported arch(%s)", package:arch()) end file:print("system = 'windows'") file:print("cpu_family = '%s'", cpu_family) file:print("cpu = '%s'", cpu) file:print("endian = 'little'") elseif package:is_plat("windows") then local cpu local cpu_family if package:is_arch("arm64") then cpu = "aarch64" cpu_family = "aarch64" elseif package:is_arch("x86") then cpu = "x86" cpu_family = "x86" elseif package:is_arch("x64") then cpu = "x86_64" cpu_family = "x86_64" else raise("unsupported arch(%s)", package:arch()) end file:print("system = 'windows'") file:print("cpu_family = '%s'", cpu_family) file:print("cpu = '%s'", cpu) file:print("endian = 'little'") elseif package:is_plat("wasm") then file:print("system = 'emscripten'") file:print("cpu_family = 'wasm32'") file:print("cpu = 'wasm32'") file:print("endian = 'little'") else local cpu = package:arch() if package:is_arch("arm64") or package:is_arch("aarch64") then cpu = "aarch64" elseif package:is_arch("arm.*") then cpu = "arm" end local cpu_family = cpu file:print("system = '%s'", package:targetos() or "linux") file:print("cpu_family = '%s'", cpu_family) file:print("cpu = '%s'", cpu) file:print("endian = 'little'") end file:print("") file:close() end return crossfile end -- get configs function _get_configs(package, configs, opt) -- add prefix configs = configs or {} table.insert(configs, "--prefix=" .. package:installdir()) table.insert(configs, "--libdir=lib") -- set build type table.insert(configs, "-Dbuildtype=" .. (package:debug() and "debug" or "release")) -- add -fpic if package:is_plat("linux") and package:config("pic") ~= false then table.insert(configs, "-Db_staticpic=true") end -- add lto if package:config("lto") then table.insert(configs, "-Db_lto=true") end -- add vs_runtime flags local vs_runtime = package:config("vs_runtime") if package:is_plat("windows") and vs_runtime then table.insert(configs, "-Db_vscrt=" .. vs_runtime:lower()) end -- add cross file if package:is_cross() then table.insert(configs, "--cross-file=" .. _get_cross_file(package, opt)) end -- add build directory table.insert(configs, _get_buildir(package, opt)) return configs end -- get msvc function _get_msvc(package) local msvc = toolchain.load("msvc", {plat = package:plat(), arch = package:arch()}) assert(msvc:check(), "vs not found!") -- we need to check vs envs if it has been not checked yet return msvc end -- get msvc run environments function _get_msvc_runenvs(package) return os.joinenvs(_get_msvc(package):runenvs()) end -- fix libname on windows function _fix_libname_on_windows(package) for _, lib in ipairs(os.files(path.join(package:installdir("lib"), "lib*.a"))) do os.mv(lib, lib:gsub("(.+)lib(.-)%.a", "%1%2.lib")) end end -- get cflags from package deps function _get_cflags_from_packagedeps(package, opt) local result = {} for _, depname in ipairs(opt.packagedeps) do local dep = type(depname) ~= "string" and depname or package:dep(depname) if dep then local fetchinfo = dep:fetch({external = false}) if fetchinfo then table.join2(result, _map_compflags(package, "cxx", "define", fetchinfo.defines)) table.join2(result, _map_compflags(package, "cxx", "includedir", fetchinfo.includedirs)) table.join2(result, _map_compflags(package, "cxx", "sysincludedir", fetchinfo.sysincludedirs)) end end end return result end -- get ldflags from package deps function _get_ldflags_from_packagedeps(package, opt) local result = {} for _, depname in ipairs(opt.packagedeps) do local dep = type(depname) ~= "string" and depname or package:dep(depname) if dep then local fetchinfo = dep:fetch({external = false}) if fetchinfo then table.join2(result, _map_linkflags(package, "binary", {"cxx"}, "linkdir", fetchinfo.linkdirs)) table.join2(result, _map_linkflags(package, "binary", {"cxx"}, "link", fetchinfo.links)) table.join2(result, _map_linkflags(package, "binary", {"cxx"}, "syslink", fetchinfo.syslinks)) end end end return result end -- get the build environments function buildenvs(package, opt) local envs = {} opt = opt or {} if package:is_plat(os.host()) then local cflags = table.join(table.wrap(package:config("cxflags")), package:config("cflags")) local cxxflags = table.join(table.wrap(package:config("cxflags")), package:config("cxxflags")) local asflags = table.wrap(package:config("asflags")) local ldflags = table.wrap(package:config("ldflags")) local shflags = table.wrap(package:config("shflags")) table.join2(cflags, opt.cflags) table.join2(cflags, opt.cxflags) table.join2(cxxflags, opt.cxxflags) table.join2(cxxflags, opt.cxflags) table.join2(asflags, opt.asflags) table.join2(ldflags, opt.ldflags) table.join2(shflags, opt.shflags) table.join2(cflags, _get_cflags_from_packagedeps(package, opt)) table.join2(cxxflags, _get_cflags_from_packagedeps(package, opt)) table.join2(ldflags, _get_ldflags_from_packagedeps(package, opt)) table.join2(shflags, _get_ldflags_from_packagedeps(package, opt)) envs.CFLAGS = table.concat(cflags, ' ') envs.CXXFLAGS = table.concat(cxxflags, ' ') envs.ASFLAGS = table.concat(asflags, ' ') envs.LDFLAGS = table.concat(ldflags, ' ') envs.SHFLAGS = table.concat(shflags, ' ') if package:is_plat("windows") then envs = os.joinenvs(envs, _get_msvc_runenvs(package)) local pkgconf = _get_pkgconfig(package) if pkgconf then envs.PKG_CONFIG = pkgconf end end end local ACLOCAL_PATH = {} local PKG_CONFIG_PATH = {} for _, dep in ipairs(package:librarydeps()) do local pkgconfig = path.join(dep:installdir(), "lib", "pkgconfig") if os.isdir(pkgconfig) then table.insert(PKG_CONFIG_PATH, pkgconfig) end pkgconfig = path.join(dep:installdir(), "share", "pkgconfig") if os.isdir(pkgconfig) then table.insert(PKG_CONFIG_PATH, pkgconfig) end end -- some binary packages contain it too. e.g. libtool for _, dep in ipairs(package:orderdeps()) do local aclocal = path.join(dep:installdir(), "share", "aclocal") if os.isdir(aclocal) then table.insert(ACLOCAL_PATH, aclocal) end end envs.ACLOCAL_PATH = path.joinenv(ACLOCAL_PATH) envs.PKG_CONFIG_PATH = path.joinenv(PKG_CONFIG_PATH) return envs end -- generate build files for ninja function generate(package, configs, opt) -- init options opt = opt or {} -- pass configurations -- TODO: support more backends https://mesonbuild.com/Commands.html#setup local argv = {"setup"} for name, value in pairs(_get_configs(package, configs, opt)) do value = tostring(value):trim() if value ~= "" then if type(name) == "number" then table.insert(argv, value) else table.insert(argv, "--" .. name .. "=" .. value) end end end -- do configure local meson = assert(find_tool("meson"), "meson not found!") os.vrunv(meson.program, argv, {envs = opt.envs or buildenvs(package, opt)}) end -- build package function build(package, configs, opt) -- generate build files opt = opt or {} generate(package, configs, opt) -- configurate build local buildir = _get_buildir(package, opt) local njob = opt.jobs or option.get("jobs") or tostring(os.default_njob()) local argv = {"compile", "-C", buildir} if option.get("diagnosis") then table.insert(argv, "-v") end table.insert(argv, "-j") table.insert(argv, njob) -- do build local meson = assert(find_tool("meson"), "meson not found!") os.vrunv(meson.program, argv, {envs = opt.envs or buildenvs(package, opt)}) end -- install package function install(package, configs, opt) -- generate build files opt = opt or {} generate(package, configs, opt) -- configure install local buildir = _get_buildir(package, opt) local argv = {"install", "-C", buildir} if option.get("verbose") then table.insert(argv, "-v") end -- do build and install local meson = assert(find_tool("meson"), "meson not found!") os.vrunv(meson.program, {"install", "-C", buildir}, {envs = opt.envs or buildenvs(package, opt)}) -- fix static libname on windows if package:is_plat("windows") and not package:config("shared") then _fix_libname_on_windows(package) end end