--!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 package.lua -- -- imports import("core.base.semver") import("core.base.option") import("core.base.global") import("core.base.hashset") import("core.base.scheduler") import("core.base.tty") import("private.async.runjobs") import("private.utils.progress") import("core.cache.memcache") import("core.cache.localcache") import("core.project.project") import("core.package.package", {alias = "core_package"}) import("actions.install", {alias = "action_install"}) import("actions.download", {alias = "action_download"}) import("devel.git") import("net.fasturl") import("repository") -- get memcache function _memcache() return memcache.cache("require.impl.package") end -- -- parse require string -- -- basic -- - add_requires("zlib") -- -- semver -- - add_requires("tbox >=1.5.1", "zlib >=1.2.11") -- -- git branch/tag -- - add_requires("zlib master") -- -- with the given repository -- - add_requires("xmake-repo@tbox >=1.5.1") -- -- with the given configs -- - add_requires("aaa_bbb_ccc >=1.5.1 <1.6.0", {optional = true, alias = "mypkg", debug = true}) -- - add_requires("tbox", {config = {coroutine = true, abc = "xxx"}}) -- -- with namespace and the 3rd package manager -- - add_requires("xmake::xmake-repo@tbox >=1.5.1") -- - add_requires("vcpkg::ffmpeg") -- - add_requires("conan::OpenSSL/1.0.2n@conan/stable") -- - add_requires("conan::openssl/1.1.1g") -- new -- - add_requires("brew::pcre2/libpcre2-8 10.x", {alias = "pcre2"}) -- -- clone as a standalone package with the different configs -- we can install and use these three packages at the same time. -- - add_requires("zlib") -- - add_requires("zlib~debug", {debug = true}) -- - add_requires("zlib~shared", {configs = {shared = true}, alias = "zlib_shared"}) -- -- pass configs to all dependent packages -- - add_requires("libpng", {deps = {system = false, configs = {shared = true, cxflags = "-DTEST"}}}) -- -- {system = nil/true/false}: -- nil: get local or system packages -- true: only get system package -- false: only get local packages -- -- function _parse_require(require_str, requires_extra, parentinfo) -- split package and version info local splitinfo = require_str:split('%s+') assert(splitinfo and #splitinfo > 0, "require(\"%s\"): invalid!", require_str) -- get package info local packageinfo = splitinfo[1] -- get version -- -- e.g. -- -- latest -- >=1.5.1 <1.6.0 -- master || >1.4 -- ~1.2.3 -- ^1.1 -- local version = "latest" if #splitinfo > 1 then version = table.concat(table.slice(splitinfo, 2), " ") end assert(version, "require(\"%s\"): unknown version!", require_str) -- require third-party packages? e.g. brew::pcre2/libpcre2-8 local reponame = nil local packagename = nil if require_str:find("::", 1, true) then packagename = packageinfo else -- get repository name, package name and package url local pos = packageinfo:lastof('@', true) if pos then -- get package name packagename = packageinfo:sub(pos + 1) -- get reponame reponame = packageinfo:sub(1, pos - 1) else packagename = packageinfo end end -- check package name assert(packagename, "require(\"%s\"): the package name not found!", require_str) -- get require extra local require_extra = {} if requires_extra then require_extra = requires_extra[require_str] or {} end -- get required building configurations local require_build_configs = require_extra.configs or require_extra.config if require_extra.debug then require_build_configs = require_build_configs or {} require_build_configs.debug = true end -- require packge in the current host platform if require_extra.host then require_extra.plat = os.host() require_extra.arch = os.arch() end -- init required item local required = {} parentinfo = parentinfo or {} required.packagename = packagename required.requireinfo = { originstr = require_str, reponame = reponame, version = version, plat = require_extra.plat, -- require package in the given platform arch = require_extra.arch, -- require package in the given architecture ["os"] = require_extra.os, -- require package in the given target os kind = require_extra.kind, -- default: library, set package kind, e.g. binary, library, we can set `kind = "binary"` to only detect binary program and ignore library. alias = require_extra.alias, -- set package alias name group = require_extra.group, -- only uses the first package in same group system = require_extra.system, -- default: true, we can set it to disable system package manually option = require_extra.option, -- set and attach option configs = require_build_configs, -- the required building configurations deps = require_extra.deps, -- the configuration passed to dependent packages default = require_extra.default, -- default: true, we can set it to disable package manually optional = parentinfo.optional or require_extra.optional, -- default: false, inherit parentinfo.optional verify = require_extra.verify, -- default: true, we can set false to ignore sha256sum and select any version external = require_extra.external, -- default: true, we use sysincludedirs/-isystem instead of -I/xxx } return required.packagename, required.requireinfo end -- load package package from system function _load_package_from_system(packagename) return core_package.load_from_system(packagename) end -- load package package from project function _load_package_from_project(packagename) return core_package.load_from_project(packagename) end -- load package package from repositories function _load_package_from_repository(packagename, reponame) local packagedir, repo = repository.packagedir(packagename, reponame) if packagedir then return core_package.load_from_repository(packagename, repo, packagedir) end end -- search packages from repositories function _search_packages(name) local packages = {} for _, packageinfo in ipairs(repository.searchdirs(name)) do local package = core_package.load_from_repository(packageinfo.name, packageinfo.repo, packageinfo.packagedir) if package then table.insert(packages, package) end end return packages end -- sort package deps -- -- e.g. -- -- a.deps = b -- b.deps = c -- -- orderdeps: c -> b -> a -- function _sort_packagedeps(package) local orderdeps = {} for _, dep in pairs(package:deps()) do table.join2(orderdeps, _sort_packagedeps(dep)) table.insert(orderdeps, dep) end return orderdeps end -- add some builtin configurations to package function _add_package_configurations(package) local vs_runtime = project.get("target.runtimes") or get_config("vs_runtime") or "MT" package:add("configs", "debug", {builtin = true, description = "Enable debug symbols.", default = false, type = "boolean"}) package:add("configs", "shared", {builtin = true, description = "Enable shared library.", default = false, type = "boolean"}) package:add("configs", "cflags", {builtin = true, description = "Set the C compiler flags."}) package:add("configs", "cxflags", {builtin = true, description = "Set the C/C++ compiler flags."}) package:add("configs", "cxxflags", {builtin = true, description = "Set the C++ compiler flags."}) package:add("configs", "asflags", {builtin = true, description = "Set the assembler flags."}) package:add("configs", "pic", {builtin = true, description = "Enable the position independent code.", default = true, type = "boolean"}) package:add("configs", "vs_runtime", {builtin = true, description = "Set vs compiler runtime.", default = vs_runtime, values = {"MT", "MTd", "MD", "MDd"}}) end -- select package version function _select_package_version(package, requireinfo) -- exists urls? otherwise be phony package (only as package group) if #package:urls() > 0 then -- has git url? local has_giturl = false for _, url in ipairs(package:urls()) do if git.checkurl(url) then has_giturl = true break end end -- select package version local source = nil local version = nil local require_version = requireinfo.version local require_verify = requireinfo.verify if (not package:get("versions") or require_verify == false) and semver.is_valid(require_version) then -- no version list in package() or need not verify sha256sum? try selecting this version directly -- @see https://github.com/xmake-io/xmake/issues/930 -- https://github.com/xmake-io/xmake/issues/1009 version = require_version source = "versions" elseif #package:versions() > 0 and (require_version == "latest" or require_version:find('.', 1, true)) then -- select version? version, source = semver.select(require_version, package:versions()) elseif has_giturl then -- select branch? version, source = require_version ~= "latest" and require_version or "master", "branches" else raise("package(%s %s): not found!", package:displayname(), require_version) end return version, source end end -- check the configurations of packages -- -- package("pcre2") -- add_configs("bitwidth", {description = "Set the code unit width.", default = "8", values = {"8", "16", "32"}}) -- add_configs("bitwidth", {type = "number", values = {8, 16, 32}}) -- add_configs("bitwidth", {restrict = function(value) if tonumber(value) < 100 then return true end}) -- function _check_package_configurations(package) local configs_defined = {} for _, name in ipairs(package:get("configs")) do configs_defined[name] = package:extraconf("configs", name) or {} end for name, value in pairs(package:configs()) do local conf = configs_defined[name] if conf then local config_type = conf.type if config_type ~= nil and type(value) ~= config_type then raise("package(%s %s): invalid type(%s) for config(%s), need type(%s)!", package:displayname(), package:version_str(), type(value), name, config_type) end if conf.values then local found = false for _, config_value in ipairs(conf.values) do if tostring(value) == tostring(config_value) then found = true break end end if not found then raise("package(%s %s): invalid value(%s) for config(%s), please run `xmake require --info %s` to get all valid values!", package:displayname(), package:version_str(), value, name, package:name()) end end if conf.restrict then if not conf.restrict(value) then raise("package(%s %s): invalid value(%s) for config(%s)!", package:displayname(), package:version_str(), value, name) end end else raise("package(%s %s): invalid config(%s), please run `xmake require --info %s` to get all configurations!", package:displayname(), package:version_str(), name, package:name()) end end end -- match require path function _match_requirepath(requirepath, requireconf) -- get pattern local function _get_pattern(pattern) pattern = pattern:gsub("([%+%.%-%^%$%(%)%%])", "%%%1") pattern = pattern:gsub("%*%*", "\001") pattern = pattern:gsub("%*", "\002") pattern = pattern:gsub("\001", ".*") pattern = pattern:gsub("\002", "[^.]*") pattern = string.ipattern(pattern, true) return pattern end -- get the excludes local excludes = requireconf:match("|.*$") if excludes then excludes = excludes:split("|", {plain = true}) end -- do match local pattern = requireconf:gsub("|.*$", "") pattern = _get_pattern(pattern) if (requirepath:match('^' .. pattern .. '$')) then -- exclude sub-deps, e.g. "libwebp.**|cmake|autoconf" local splitinfo = requirepath:split(".", {plain = true}) if #splitinfo > 0 then local name = splitinfo[#splitinfo] for _, exclude in ipairs(excludes) do pattern = _get_pattern(exclude) if (name:match('^' .. pattern .. '$')) then return false end end end return true end end -- merge requireinfo from `add_requireconfs()` -- -- add_requireconfs("*", {system = false, configs = {vs_runtime = "MD"}}) -- add_requireconfs("lib*", {system = false, configs = {vs_runtime = "MD"}}) -- add_requireconfs("libwebp", {system = false, configs = {vs_runtime = "MD"}}) -- add_requireconfs("libpng.zlib", {system = false, override = true, configs = {cxflags = "-DTEST1"}, version = "1.2.10"}) -- add_requireconfs("libtiff.*", {system = false, configs = {cxflags = "-DTEST2"}}) -- add_requireconfs("libwebp.**|cmake|autoconf", {system = false, configs = {cxflags = "-DTEST3"}}) -- recursive deps -- function _merge_requireinfo(requireinfo, requirepath) -- find requireconf from the given requirepath local requireconf_result = {} local requireconfs, requireconfs_extra = project.requireconfs_str() if requireconfs then for _, requireconf in ipairs(requireconfs) do if _match_requirepath(requirepath, requireconf) then local requireconf_extra = requireconfs_extra[requireconf] table.insert(requireconf_result, {requireconf = requireconf, requireconf_extra = requireconf_extra}) end end end -- append requireconf_extra into requireinfo -- and the configs of add_requires have a higher priority than add_requireconfs. -- -- e.g. -- add_requireconfs("*", {configs = {debug = false}}) -- add_requires("foo", "bar", {configs = {debug = true}}) -- -- foo and bar will be debug mode -- -- we can also override the configs of add_requires -- -- e.g. -- add_requires("zlib 1.2.11") -- add_requireconfs("zlib", {override = true, version = "1.2.10"}) -- -- we override the version of zlib to 1.2.10 -- if #requireconf_result == 1 then local requireconf_extra = requireconf_result[1].requireconf_extra if requireconf_extra then -- preprocess requireconf_extra, (debug, override ..) local override = requireconf_extra.override requireconf_extra.override = nil if requireconf_extra.debug then requireconf_extra.configs = requireconf_extra.configs or {} requireconf_extra.configs.debug = true requireconf_extra.debug = nil end -- append or override configs and extra options for k, v in pairs(requireconf_extra.configs) do requireinfo.configs = requireinfo.configs or {} if override or requireinfo.configs[k] == nil then requireinfo.configs[k] = v end end for k, v in pairs(requireconf_extra) do if k ~= "configs" then if override or requireinfo[k] == nil then requireinfo[k] = v end end end end elseif #requireconf_result > 1 then local confs = {} for _, item in ipairs(requireconf_result) do table.insert(confs, item.requireconf) end raise("package(%s) will match multiple add_requireconfs(%s)!", requirepath, table.concat(confs, " ")) end end -- get package key function _get_packagekey(packagename, requireinfo, version) local key = packagename .. "/" .. (version or requireinfo.version) local configs = requireinfo.configs if configs then local configs_order = {} for k, v in pairs(configs) do table.insert(configs_order, k .. "=" .. tostring(v)) end table.sort(configs_order) key = key .. ":" .. string.serialize(configs_order, true) end return key end -- inherit some builtin configs of parent package if these config values are not default value -- e.g. add_requires("libpng", {configs = {vs_runtime = "MD", pic = false}}) -- function _inherit_parent_configs(requireinfo, parentinfo) local requireinfo_configs = requireinfo.configs or {} local parentinfo_configs = parentinfo.configs or {} if not requireinfo_configs.shared then if requireinfo_configs.vs_runtime == nil then requireinfo_configs.vs_runtime = parentinfo_configs.vs_runtime end if requireinfo_configs.pic == nil then requireinfo_configs.pic = parentinfo_configs.pic end end requireinfo.configs = requireinfo_configs end -- load required packages function _load_package(packagename, requireinfo, opt) -- strip trailng ~tag, e.g. zlib~debug local displayname if packagename:find('~', 1, true) then displayname = packagename packagename = packagename:gsub("~.+$", "") requireinfo.alias = requireinfo.alias or displayname end -- load package from project first local package if os.isfile(os.projectfile()) then package = _load_package_from_project(packagename) end -- load package from repositories if not package then package = _load_package_from_repository(packagename, requireinfo.reponame) end -- load package from system if not package and opt.system ~= false then package = _load_package_from_system(packagename) end -- check assert(package, "package(%s) not found!", packagename) -- merge requireinfo from `add_requireconfs()` _merge_requireinfo(requireinfo, opt.requirepath) -- inherit some builtin configs of parent package, e.g. vs_runtime, pic if opt.parentinfo and package:kind() ~= "binary" then _inherit_parent_configs(requireinfo, opt.parentinfo) end -- select package version local version, source = _select_package_version(package, requireinfo) if version then package:version_set(version, source) end -- get package key local packagekey = _get_packagekey(packagename, requireinfo, version) -- It exists conflict for dependent packages for each root packages? resolve it first -- e.g. -- add_requires("foo") -> bar -> zlib 1.2.10 -- -> xyz -> zlib 1.2.11 or other configs -- -- add_requires("ddd") -> zlib -- -- We assume that there is no conflict between `foo` and `ddd`. -- -- Of course, conflicts caused by `add_packages("foo", "ddd")` -- cannot be detected at present and can only be resolved by the user -- local rootkey = opt.rootkey local packagekey_prev = _memcache():get3("packages_root", rootkey, packagename) if packagekey_prev then if packagekey_prev and packagekey_prev ~= packagekey then raise("package(%s): conflict dependences with package(%s)!", packagekey, packagekey_prev) end end _memcache():set3("packages_root", rootkey, packagename, packagekey) -- get package from cache first local package_cached = _memcache():get2("packages", packagekey) if package_cached then return package_cached end -- save require info package:requireinfo_set(requireinfo) -- save display name if not displayname then local packageid = _memcache():get2("packageids", packagename) displayname = packagename if packageid then displayname = displayname .. "#" .. tostring(packageid) end _memcache():set2("packageids", packagename, (packageid or 0) + 1) end package:displayname_set(displayname) -- disable parallelize if the package cache directory conflicts local cachedirs = _memcache():get2("cachedirs", package:cachedir()) if cachedirs then package:set("parallelize", false) end _memcache():set2("cachedirs", package:cachedir(), true) -- add some builtin configurations to package _add_package_configurations(package) -- check package configurations _check_package_configurations(package) -- do load local on_load = package:script("load") if on_load then on_load(package) end -- load environments from the manifest to enable the environments of on_install() package:envs_load() -- save this package package to cache _memcache():set2("packages", packagekey, package) return package end -- load all required packages function _load_packages(requires, opt) -- no requires? if not requires or #requires == 0 then return {} end -- load packages local packages = {} for _, requireitem in ipairs(load_requires(requires, opt.requires_extra, opt)) do -- load package local rootkey = opt.rootkey or requireitem.name local requireinfo = requireitem.info local requirepath = opt.requirepath and (opt.requirepath .. "." .. requireitem.name) or requireitem.name local package = _load_package(requireitem.name, requireinfo, table.join(opt, {rootkey = rootkey, requirepath = requirepath})) -- maybe package not found and optional if package then -- load dependent packages and save them first of this package if not package._DEPS then local deps = package:get("deps") if deps and opt.nodeps ~= true then -- load dependent packages and do not load system/3rd packages for package/deps() local packagedeps = {} for _, dep in ipairs(_load_packages(deps, {rootkey = rootkey, requirepath = requirepath, requires_extra = package:extraconf("deps") or {}, parentinfo = requireinfo, nodeps = opt.nodeps, system = false})) do dep:parents_add(package) table.insert(packages, dep) packagedeps[dep:name()] = dep end package._DEPS = packagedeps package._ORDERDEPS = table.unique(_sort_packagedeps(package)) end end -- save this package package table.insert(packages, package) end end return packages end -- sort packages urls function _sort_packages_urls(packages) -- add all urls to fasturl and prepare to sort them together for _, package in pairs(packages) do fasturl.add(package:urls()) end -- sort and update urls for _, package in pairs(packages) do package:urls_set(fasturl.sort(package:urls())) end end -- get package parents string function _get_package_parents_str(package) local parents = package:parents() if parents then local parentnames = {} for _, parent in pairs(parents) do table.insert(parentnames, parent:displayname()) end if #parentnames == 0 then return end return table.concat(parentnames, ",") end end -- get package configs string function _get_package_configs_str(package) local configs = {} if package:optional() then table.insert(configs, "optional") end local requireinfo = package:requireinfo() if requireinfo then for k, v in pairs(requireinfo.configs) do if type(v) == "boolean" then table.insert(configs, k .. ":" .. (v and "y" or "n")) else table.insert(configs, k .. ":" .. v) end end end local parents_str = _get_package_parents_str(package) if parents_str then table.insert(configs, "from:" .. parents_str) end local configs_str = #configs > 0 and "[" .. table.concat(configs, ", ") .. "]" or "" local limitwidth = os.getwinsize().width * 2 / 3 if #configs_str > limitwidth then configs_str = configs_str:sub(1, limitwidth) .. " ..)" end return configs_str end -- get user confirm function _get_confirm(packages) -- no confirmed packages? if #packages == 0 then return true end -- get confirm local confirm = utils.confirm({default = true, description = function () -- get packages for each repositories local packages_repo = {} local packages_group = {} for _, package in ipairs(packages) do -- achive packages by repository local reponame = package:repo() and package:repo():name() or (package:isSys() and "system" or "") if package:is3rd() then reponame = package:name():lower():split("::")[1] end packages_repo[reponame] = packages_repo[reponame] or {} table.insert(packages_repo[reponame], package) -- achive packages by group local group = package:group() if group then packages_group[group] = packages_group[group] or {} table.insert(packages_group[group], package) end end -- show tips cprint("${bright color.warning}note: ${clear}try installing these packages (pass -y to skip confirm)?") for reponame, packages in pairs(packages_repo) do if reponame ~= "" then print("in %s:", reponame) end local packages_showed = {} for _, package in ipairs(packages) do if not packages_showed[tostring(package)] then local group = package:group() if group and packages_group[group] and #packages_group[group] > 1 then for idx, package_in_group in ipairs(packages_group[group]) do cprint(" ${yellow}%s${clear} %s %s ${dim}%s", idx == 1 and "->" or " or", package_in_group:displayname(), package_in_group:version_str() or "", _get_package_configs_str(package_in_group)) packages_showed[tostring(package_in_group)] = true end packages_group[group] = nil else cprint(" ${yellow}->${clear} %s %s ${dim}%s", package:displayname(), package:version_str() or "", _get_package_configs_str(package)) packages_showed[tostring(package)] = true end end end end end}) return confirm end -- install packages function _install_packages(packages_install, packages_download) -- we need hide wait characters if is not a tty local show_wait = io.isatty() -- do install local progress_helper = show_wait and progress.new() or nil local packages_installing = {} local packages_downloading = {} local packages_pending = table.copy(packages_install) local packages_in_group = {} local installing_count = 0 local parallelize = true runjobs("install_packages", function (index) -- fetch a new package local package = nil while package == nil and #packages_pending > 0 do for idx, pkg in ipairs(packages_pending) do -- all dependences has been installed? we install it now local ready = true local dep_not_found = nil for _, dep in ipairs(pkg:orderdeps()) do if not dep:exists() then ready = false dep_not_found = dep break end end local group = pkg:group() if ready and group then -- this group has been installed? skip it local group_status = packages_in_group[group] if group_status == 1 then table.remove(packages_pending, idx) break -- this group is installing? wait it elseif group_status == 0 then ready = false end end -- get a package with the ready status if ready then package = pkg table.remove(packages_pending, idx) break elseif #packages_pending == 1 and dep_not_found then raise("package(%s): cannot be installed, there are dependencies(%s) that cannot be installed!", pkg:displayname(), dep_not_found:displayname()) elseif #packages_pending == 1 then raise("package(%s): cannot be installed!", pkg:displayname()) end end if package == nil and #packages_pending > 0 then scheduler.co_yield() end end if package then -- only install the first package in same group local group = package:group() if not group or not packages_in_group[group] then -- disable parallelize? if not package:parallelize() then parallelize = false end if not parallelize then while installing_count > 0 do scheduler.co_yield() end end installing_count = installing_count + 1 -- mark this group as 'installing' if group then packages_in_group[group] = 0 end -- download this package first local downloaded = true if packages_download[tostring(package)] then packages_downloading[index] = package downloaded = action_download(package) packages_downloading[index] = nil end -- install this package packages_installing[index] = package if downloaded then action_install(package) end packages_installing[index] = nil -- mark this group as 'installed' or 'failed' if group then packages_in_group[group] = package:exists() and 1 or -1 end -- enable parallelize parallelize = true installing_count = installing_count - 1 end end packages_installing[index] = nil packages_downloading[index] = nil end, {total = #packages_install, comax = (option.get("verbose") or option.get("diagnosis")) and 1 or 4, on_timer = function (running_jobs_indices) -- do not print progress info if be verbose if option.get("verbose") or not show_wait then return end -- make installing and downloading packages list local installing = {} local downloading = {} for _, index in ipairs(running_jobs_indices) do local package = packages_installing[index] if package then table.insert(installing, package:displayname()) end local package = packages_downloading[index] if package then table.insert(downloading, package:displayname()) end end -- get waitobjs tips local tips = nil local waitobjs = scheduler.co_group_waitobjs("install_packages") if waitobjs:size() > 0 then local names = {} for _, obj in waitobjs:keys() do if obj:otype() == scheduler.OT_PROC then table.insert(names, obj:name()) elseif obj:otype() == scheduler.OT_SOCK then table.insert(names, "sock") elseif obj:otype() == scheduler.OT_PIPE then table.insert(names, "pipe") end end names = table.unique(names) if #names > 0 then names = table.concat(names, ",") if #names > 16 then names = names:sub(1, 16) .. ".." end tips = string.format("(%d/%s)", waitobjs:size(), names) end end -- trace progress_helper:clear() tty.erase_line_to_start().cr() cprintf("${yellow} => ") if #downloading > 0 then cprintf("downloading ${magenta}%s", table.concat(downloading, ", ")) end if #installing > 0 then cprintf("%sinstalling ${magenta}%s", #downloading > 0 and ", " or "", table.concat(installing, ", ")) end cprintf(" .. %s", tips and ("${dim}" .. tips .. "${clear} ") or "") progress_helper:write() end, exit = function(errors) if errors then tty.erase_line_to_start().cr() io.flush() end end}) end -- the cache directory function cachedir() return path.join(global.directory(), "cache", "packages") end -- load requires function load_requires(requires, requires_extra, opt) opt = opt or {} local requireitems = {} for _, require_str in ipairs(requires) do local packagename, requireinfo = _parse_require(require_str, requires_extra, opt.parentinfo) table.insert(requireitems, {name = packagename, info = requireinfo}) end return requireitems end -- load all required packages function load_packages(requires, opt) opt = opt or {} local unique = {} local packages = {} for _, package in ipairs(_load_packages(requires, opt)) do local key = _get_packagekey(package:name(), package:requireinfo()) if not unique[key] then table.insert(packages, package) unique[key] = true end end return packages end -- install packages function install_packages(requires, opt) -- init options opt = opt or {} -- load packages local packages = load_packages(requires, opt) -- fetch packages (with system) from local first runjobs("fetch_packages", function (index) local package = packages[index] if package and (not option.get("force") or (option.get("shallow") and package:parents())) then package:envs_enter() package:fetch() package:envs_leave() end end, {total = #packages}) -- filter packages local packages_install = {} local packages_download = {} local packages_unsupported = {} for _, package in ipairs(packages) do if not package:exists() then if package:supported() then if #package:urls() > 0 then packages_download[tostring(package)] = package end table.insert(packages_install, package) elseif not package:optional() then table.insert(packages_unsupported, package) end end end -- exists unsupported packages? if #packages_unsupported > 0 then -- show tips cprint("${bright color.warning}note: ${clear}the following packages are unsupported for $(plat)/$(arch)!") for _, package in ipairs(packages_unsupported) do print(" -> %s %s", package:displayname(), package:version_str() or "") end raise() end -- get user confirm if not _get_confirm(packages_install) then local packages_must = {} for _, package in ipairs(packages_install) do if not package:optional() then table.insert(packages_must, package:displayname()) end end if #packages_must > 0 then raise("packages(%s): must be installed!", table.concat(packages_must, ", ")) else -- continue other actions return end end -- sort package urls _sort_packages_urls(packages_download) -- install all required packages from repositories _install_packages(packages_install, packages_download) -- ok return packages end -- uninstall packages function uninstall_packages(requires, opt) -- init options opt = opt or {} -- do not remove dependent packages opt.nodeps = true -- clear the local cache localcache.clear() -- remove all packages local packages = {} for _, instance in ipairs(load_packages(requires, opt)) do if os.isfile(instance:manifest_file()) then table.insert(packages, instance) end os.tryrm(instance:installdir()) end return packages end -- export packages function export_packages(requires, opt) -- init options opt = opt or {} -- get the export directory local exportdir = assert(opt.exportdir) -- export all packages local packages = {} for _, instance in ipairs(load_packages(requires, opt)) do -- get the exported name local name = instance:name():lower():gsub("::", "_") if instance:version_str() then name = name .. "_" .. instance:version_str() end name = name .. "_" .. instance:buildhash() -- export this package if instance:fetch() then os.cp(instance:installdir(), path.join(exportdir, name)) table.insert(packages, instance) end end return packages end -- search packages function search_packages(names) -- search all names local results = {} for _, name in ipairs(names) do local packages = _search_packages(name) if packages then results[name] = packages end end return results end