--!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 package.lua -- -- define module local package = {} local _instance = _instance or {} -- load modules local io = require("base/io") local os = require("base/os") local path = require("base/path") local table = require("base/table") local utils = require("base/utils") local semver = require("base/semver") local rule = require("project/rule") local config = require("project/config") local sandbox = require("sandbox/sandbox") local localcache = require("cache/localcache") local instance_deps = require("base/private/instance_deps") -- save the requires info to the cache function _instance:save() package._cache():set(self:name(), self._INFO) package._cache():save() end -- clear the package function _instance:clear() local info = self._INFO if info then for k, v in pairs(info) do if not k:startswith("__") then info[k] = nil end end end self._COMPONENT_DEPS = nil end -- dump this package function _instance:dump() utils.dump(self._INFO) end -- get the require info function _instance:get(infoname) return self._INFO[infoname] end -- get the package name (with alias name) function _instance:name() return self._NAME end -- get the full name function _instance:fullname() local namespace = self:namespace() return namespace and namespace .. "::" .. self:name() or self:name() end -- get the package version function _instance:version() -- get it from cache first if self._VERSION ~= nil then return self._VERSION end -- get version local version = nil local verstr = self:get("version") if verstr then version = semver.new(verstr) end self._VERSION = version or false return version end -- get the package license function _instance:license() return self:get("license") end -- has static libraries? function _instance:has_static() return self:get("static") end -- has shared libraries? function _instance:has_shared() return self:get("shared") end -- get the require string function _instance:requirestr() return self:get("__requirestr") end -- get the namespace function _instance:namespace() return self:get("__namespace") end -- get the require configuration from the given name -- -- e.g. -- -- add_requires("xxx", {system = true, configs = {shared = true}}) -- -- local configs = pkg:requireconf() -- local system = pkg:requireconf("system") -- local shared = pkg:requireconf("configs", "shared") -- function _instance:requireconf(name, key) local requireconfs = self:get("__requireconfs") local value = requireconfs if name then value = requireconfs and requireconfs[name] or nil if value and key then value = value[key] end end return value end -- get the install directory -- @see https://github.com/xmake-io/xmake/issues/3106 function _instance:installdir() return self:get("installdir") or self:get("__installdir") -- deprecated end -- get library files function _instance:libraryfiles() return self:get("libfiles") end -- get components function _instance:components() return self:get("components") end -- get default components function _instance:components_default() return self:get("__components_default") end -- get components list with link order function _instance:components_orderlist() return self:get("__components_orderlist") end -- get the dependencies of components function _instance:components_deps() return self:get("__components_deps") end -- get user extra configuration from package/on_fetch -- @see https://github.com/xmake-io/xmake/issues/3106#issuecomment-1330143922 -- -- e.g. -- -- @code -- package("xxx") -- on_fetch(function (package) -- return {includedirs = "", links = "", extras = {foo = ""}} -- end) -- -- @endcode -- -- we can also get extra configuration from package/add_xxx -- -- e.g. -- -- @code -- package("xxx") -- add_linkgroups("foo", {group = true}) -- -- target:pkg("xxx"):extraconf("linkgroups", "foo", "group") -- @endcode -- -- extras = { -- linkgroups = { -- z = { -- group = true -- } -- } -- } -- function _instance:extraconf(name, item, key) local extraconfs = self:get("extras") if not extraconfs then return end -- get configuration local extraconf = extraconfs[name] -- get configuration value local value = extraconf if item then value = extraconf and extraconf[item] or nil if value == nil and extraconf and type(item) == "table" then value = extraconf[table.concat(item, "_")] end if value and key then value = value[key] end end return value end -- add extra configuration function _instance:extraconf_add(name, item) local extraconfs = self:get("extras") if not extraconfs then extraconfs = {} end local extraconf = extraconfs[name] if extraconf ~= nil then extraconf = table.wrap(extraconf) table.join2(extraconf, item) else extraconf = item end extraconfs[name] = extraconf self:set("extras", extraconfs) end -- get order dependencies of the given component function _instance:component_orderdeps(name) local component_orderdeps = self._COMPONENT_ORDERDEPS if not component_orderdeps then component_orderdeps = {} self._COMPONENT_ORDERDEPS = component_orderdeps end -- expand dependencies local orderdeps = component_orderdeps[name] if not orderdeps then orderdeps = table.reverse_unique(self:_sort_componentdeps(name)) component_orderdeps[name] = orderdeps end return orderdeps end -- set the value to the requires info function _instance:set(name_or_info, ...) if type(name_or_info) == "string" then local args = ... if args ~= nil then self._INFO[name_or_info] = table.unwrap(table.unique(table.join(...))) else self._INFO[name_or_info] = nil end elseif table.is_dictionary(name_or_info) then for name, info in pairs(table.join(name_or_info, ...)) do self:set(name, info) end end end -- add the value to the requires info function _instance:add(name_or_info, ...) if type(name_or_info) == "string" then local name = name_or_info if name == "extras" then for _, extraconf in ipairs({...}) do for k, v in pairs(extraconf) do self:extraconf_add(k, v) end end else local info = table.wrap(self._INFO[name]) self._INFO[name] = table.unwrap(table.unique(table.join(info, ...))) end elseif table.is_dictionary(name_or_info) then for name, info in pairs(table.join(name_or_info, ...)) do self:add(name, info) end end end -- this require info is enabled? function _instance:enabled() return self:get("__enabled") end -- enable or disable this require info function _instance:enable(enabled) self:set("__enabled", enabled) end -- get environments function _instance:envs() return self:get("envs") end -- get the given rule function _instance:rule(name) return self:rules()[name] end -- get package rules -- @see https://github.com/xmake-io/xmake/issues/2374 function _instance:rules() local rules = self._RULES if rules == nil then local ruleinfos = {} local installdir = self:installdir() local rulesdir = path.join(installdir, "rules") if os.isdir(rulesdir) then local files = os.filedirs(path.join(rulesdir, "*")) if files then for _, filepath in ipairs(files) do local results, errors if filepath:endswith(".lua") then results, errors = rule._load(filepath) elseif os.isdir(filepath) and os.isfile(path.join(filepath, "xmake.lua")) then results, errors = rule._load(path.join(filepath, "xmake.lua")) else os.raise("unknown rule %s: %s", os.isdir(filepath) and "directory" or "file", filepath) end if results then table.join2(ruleinfos, results) else os.raise(errors) end end end end -- make rule instances rules = {} for rulename, ruleinfo in pairs(ruleinfos) do local rulename = "@" .. self:name() .. "/" .. rulename local instance = rule.new(rulename, ruleinfo, {package = self}) if instance:script("load") then utils.warning("we cannot add `on_load()` in package rule(%s), please use `on_config()` instead of it!", rulename) end if instance:script("load_after") then utils.warning("we cannot add `after_load()` in package rule(%s), please use `on_config()` instead of it!", rulename) end rules[rulename] = instance end self._RULES = rules end return rules end -- sort component deps function _instance:_sort_componentdeps(name) local orderdeps = {} local plaindeps = self:components_deps() and self:components_deps()[name] for _, dep in ipairs(table.wrap(plaindeps)) do table.insert(orderdeps, dep) table.join2(orderdeps, self:_sort_componentdeps(dep)) end return orderdeps end -- we need to sort package set keys by this string -- @see https://github.com/xmake-io/xmake/pull/2971#issuecomment-1290052169 function _instance:__tostring() return "" end -- get cache function package._cache() return localcache.cache("package") end -- load the package from the cache function package.load(name) local info = package._cache():get(name) if info == nil then return end return package.load_withinfo(name, info) end -- load package from the give package info function package.load_withinfo(name, info) local instance = table.inherit(_instance) instance._INFO = info instance._NAME = name return instance end -- return module return package