--!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, Xmake Open Source Community. -- -- @author ruki -- @file find_package.lua -- -- imports import("core.base.option") import("core.base.semver") import("core.project.target") import("lib.detect.find_tool") import("private.core.base.is_cross") import("package.manager.pkgconfig.find_package", {alias = "find_package_from_pkgconfig"}) import("get_package_name") -- find package from list of file inside pacman package function _find_package_from_list(list, name, pacman, opt) -- mingw + pacman = cygpath available local cygpath = nil local pathtomsys = nil local msystem = nil if is_subhost("msys") and opt.plat == "mingw" then cygpath = find_tool("cygpath") if not cygpath then return end pathtomsys = os.iorunv(cygpath.program, {"--windows", "/"}) pathtomsys = pathtomsys:trim() msystem = os.getenv("MSYSTEM") if msystem then msystem = msystem:lower() end end -- iterate over each file path inside the pacman package local result = {} for _, line in ipairs(list:split('\n', {plain = true})) do -- on msys cygpath should be used to convert local path to windows path local line = line:trim():split('%s+')[2] if line:find("/include/", 1, true) and (line:endswith(".h") or line:endswith(".hpp")) then if not line:startswith("/usr/include/") then if not (msystem and line:startswith("/" .. msystem .. "/include/")) then result.includedirs = result.includedirs or {} local hpath = line if is_subhost("msys") and opt.plat == "mingw" then hpath = path.join(pathtomsys, line) local basehpath = path.join(pathtomsys, msystem .. "/include") table.insert(result.includedirs, basehpath) end table.insert(result.includedirs, path.directory(hpath)) end end -- remove lib and .a, .dll.a and .so to have the links elseif line:endswith(".dll.a") then -- only for mingw local apath = path.join(pathtomsys, line) apath = apath:trim() result.linkdirs = result.linkdirs or {} result.links = result.links or {} table.insert(result.linkdirs, path.directory(apath)) table.insert(result.links, target.linkname(path.filename(apath), {plat = opt.plat})) elseif line:endswith(".so") then result.linkdirs = result.linkdirs or {} result.links = result.links or {} table.insert(result.linkdirs, path.directory(line)) table.insert(result.links, target.linkname(path.filename(line), {plat = opt.plat})) elseif line:endswith(".a") then result.linkdirs = result.linkdirs or {} result.links = result.links or {} local apath = line if is_subhost("msys") and opt.plat == "mingw" then apath = path.join(pathtomsys, line) apath = apath:trim() end table.insert(result.linkdirs, path.directory(apath)) table.insert(result.links, target.linkname(path.filename(apath), {plat = opt.plat})) end end if result.includedirs then result.includedirs = table.unique(result.includedirs) end if result.linkdirs then result.linkdirs = table.unique(result.linkdirs) end if result.links then result.links = table.reverse_unique(result.links) end -- use pacman package version as version local version = try { function() return os.iorunv(pacman.program, {"-Q", name}) end } if version then version = version:trim():split('%s+')[2] -- we need strip "1:" prefix, @see https://github.com/xmake-io/xmake/issues/2020 -- e.g. vulkan-headers 1:1.3.204-1 if version:startswith("1:") then version = version:split(':')[2] end result.version = version:split('-')[1] else result = nil end return result end -- find libfiles from list of file inside pacman package function _find_libfiles_from_list(list, name, pacman, opt) -- mingw + pacman = cygpath available local cygpath = nil local pathtomsys = nil if is_subhost("msys") and opt.plat == "mingw" then cygpath = find_tool("cygpath") if not cygpath then return end pathtomsys = os.iorunv(cygpath.program, {"--windows", "/"}) pathtomsys = pathtomsys:trim() end -- iterate over each file path inside the pacman package local libfiles for _, line in ipairs(list:split('\n', {plain = true})) do -- on msys cygpath should be used to convert local path to windows path local line = line:trim():split('%s+')[2] if line:endswith(".dll.a") then -- only for mingw local apath = path.join(pathtomsys, line) apath = apath:trim() libfiles = libfiles or {} table.insert(libfiles, apath) elseif line:endswith(".so") then libfiles = libfiles or {} table.insert(libfiles, line) elseif line:endswith(".a") then local apath = line if is_subhost("msys") and opt.plat == "mingw" then apath = path.join(pathtomsys, line) apath = apath:trim() end libfiles = libfiles or {} table.insert(libfiles, apath) end end return libfiles end -- find package from the system directories -- -- @param name the package name -- @param opt the options, e.g. {verbose = true, version = "1.12.x") -- function main(name, opt) opt = opt or {} if is_cross(opt.plat, opt.arch) then return end -- find pacman local pacman = find_tool("pacman") if not pacman then return end -- get package name name = get_package_name(name, opt) -- get package files list local list = name and try { function() return os.iorunv(pacman.program, {"-Q", "-l", name}) end } if not list then return end -- parse package files list local linkdirs = {} local pkgconfig_files = {} for _, line in ipairs(list:split('\n', {plain = true})) do local line = line:trim():split('%s+')[2] if line:find("/pkgconfig/", 1, true) and line:endswith(".pc") then table.insert(pkgconfig_files, line) end if line:endswith(".so") or line:endswith(".a") or line:endswith(".lib") then table.insert(linkdirs, path.directory(line)) end end linkdirs = table.unique(linkdirs) -- we iterate over each pkgconfig file to extract the required data local result for _, pkgconfig_file in ipairs(pkgconfig_files) do local pkgconfig_dir = path.directory(pkgconfig_file) local pkgconfig_name = path.basename(pkgconfig_file) local pcresult = find_package_from_pkgconfig(pkgconfig_name, {configdirs = pkgconfig_dir, linkdirs = linkdirs}) if pcresult then result = result or {} for _, includedir in ipairs(pcresult.includedirs) do result.includedirs = result.includedirs or {} table.insert(result.includedirs, includedir) end for _, linkdir in ipairs(pcresult.linkdirs) do result.linkdirs = result.linkdirs or {} table.insert(result.linkdirs, linkdir) end for _, link in ipairs(pcresult.links) do result.links = result.links or {} table.insert(result.links, link) end for _, libfile in ipairs(pcresult.libfiles) do result.libfiles = result.libfiles or {} table.insert(result.libfiles, libfile) end result.shared = pcresult.shared result.static = pcresult.static end end if result then if result.includedirs then result.includedirs = table.unique(result.includedirs) end if result.linkdirs then result.linkdirs = table.unique(result.linkdirs) end if result.libfiles then result.libfiles = table.unique(result.libfiles) end if result.links then result.links = table.reverse_unique(result.links) end -- We should get version from pacman, because if a pacman package contains multiples .pc we may get a wrong version local verstr = try { function() return os.iorunv(pacman.program, {"-Q", name}) end } if verstr then local version = semver.match(verstr) if version then result.version = version:rawstr():split('-')[1] end end else -- if there is no .pc, we parse the package content to obtain the data we want result = _find_package_from_list(list, name, pacman, opt) end if result then -- we give priority to libfiles in the list local libfiles = _find_libfiles_from_list(list, name, pacman, opt) if libfiles then result.shared = nil result.static = nil result.libfiles = libfiles for _, libfile in ipairs(libfiles) do if libfile:endswith(".so") then result.shared = true elseif libfile:endswith(".a") then result.static = true end end end end return result end