diff options
Diffstat (limited to 'xmake/core/package/package.lua')
| -rw-r--r-- | xmake/core/package/package.lua | 219 |
1 files changed, 201 insertions, 18 deletions
diff --git a/xmake/core/package/package.lua b/xmake/core/package/package.lua index 0d164ce95..3d98a3743 100644 --- a/xmake/core/package/package.lua +++ b/xmake/core/package/package.lua @@ -37,6 +37,7 @@ local interpreter = require("base/interpreter") local select_script = require("base/private/select_script") local is_cross = require("base/private/is_cross") local memcache = require("cache/memcache") +local addon = require("package/addon") local toolchain = require("tool/toolchain") local compiler = require("tool/compiler") local linker = require("tool/linker") @@ -54,6 +55,16 @@ local sandbox_os = require("sandbox/modules/os") local sandbox_module = require("sandbox/modules/import/core/sandbox/module") -- new an instance +-- +-- @param name the package name, the namespace prefix will be stripped and saved separately, +-- e.g. "zlib", "myns::zlib", but "vcpkg::zlib" will be kept as a whole, +-- because `vcpkg` is a package manager, but not a namespace +-- @param info the package description scope info +-- @param opt the options +-- - scriptdir: the directory of the package description file, the relative paths in +-- this package will be relative to it +-- - repo: the repository instance which this package belongs to +-- function _instance.new(name, info, opt) opt = opt or {} local instance = table.inherit(_instance) @@ -610,6 +621,23 @@ function _instance:is_toolchain() return self:kind() == "toolchain" end +-- is addon package? +-- +-- it will be installed to `~/.xmake/addons/<name>/<version>` and +-- it can provide plugins, rules, toolchains, templates and modules for xmake +-- +function _instance:is_addon() + return self:kind() == "addon" +end + +-- is plugin package? +-- +-- @note this kind is deprecated, please use the `addon` kind instead +-- +function _instance:is_plugin() + return self:kind() == "plugin" +end + -- is library package? -- -- @return true if the package kind is "library" or default @@ -907,20 +935,32 @@ function _instance:installdir(...) installdir = self:get("installdir") if not installdir then local name = self:name():lower():gsub("::", "_") - if self:is_local() then - installdir = path.join(package.installdir({localdir = true}), name:sub(1, 1):lower(), name) - else - installdir = path.join(package.installdir(), name:sub(1, 1):lower(), name) - end - local version_str = self:version_str() - if version_str then - -- strip invalid characters on windows, e.g. `>= <=` + if self:is_addon() then + -- e.g. ~/.xmake/addons/<name>/<version> + local version_str = self:version_str() or "latest" if os.is_host("windows") then version_str = version_str:gsub("[>=<|%*]", "") end - installdir = path.join(installdir, version_str) + installdir = addon.addondir(self:name(), version_str) + elseif self:is_plugin() then + -- deprecated, @see the `addon` kind + installdir = path.join(global.directory(), "plugins", name) + else + if self:is_local() then + installdir = path.join(package.installdir({localdir = true}), name:sub(1, 1):lower(), name) + else + installdir = path.join(package.installdir(), name:sub(1, 1):lower(), name) + end + local version_str = self:version_str() + if version_str then + -- strip invalid characters on windows, e.g. `>= <=` + if os.is_host("windows") then + version_str = version_str:gsub("[>=<|%*]", "") + end + installdir = path.join(installdir, version_str) + end + installdir = path.join(installdir, self:buildhash()) end - installdir = path.join(installdir, self:buildhash()) end self._INSTALLDIR = installdir end @@ -1136,7 +1176,7 @@ function _instance:_load() if on_load then on_load(self) end - + -- load all components self:_load_components() @@ -1174,6 +1214,7 @@ function _instance:_rawenvs() envs.DYLD_LIBRARY_PATH = {"lib"} end end + self._RAWENVS = envs end return envs @@ -2076,11 +2117,17 @@ function _instance:fetch(opt) -- always install to the local project directory? -- @see https://github.com/xmake-io/xmake/pull/4376 + -- + -- @note the host packages are the tools which build the other packages, e.g. the toolchains, + -- they do not depend on the project configuration and they are shared between the projects, + -- so they are only installed locally with their own policy + -- @see https://github.com/xmake-io/xmake/issues/7716 + local policyname = self:is_host() and "package.host.install_locally" or "package.install_locally" local install_locally - if project and project.policy("package.install_locally") then + if project and project.policy(policyname) then install_locally = true end - if install_locally == nil and self:policy("package.install_locally") then + if install_locally == nil and self:policy(policyname) then install_locally = true end if not self:is_local() and install_locally and system ~= true then @@ -2488,9 +2535,20 @@ function _instance:_generate_runtime_configs(sourcekind) self.sourcekinds = function (self) return sourcekind end - configs.cxflags = self:compiler(sourcekind):map_flags("runtime", runtimes, {target = self}) - configs.ldflags = self:linker("binary", sourcekind):map_flags("runtime", runtimes, {target = self}) - configs.shflags = self:linker("shared", sourcekind):map_flags("runtime", runtimes, {target = self}) + local cxflags = self:compiler(sourcekind):map_flags("runtime", runtimes, {target = self}) + local ldflags = self:linker("binary", sourcekind):map_flags("runtime", runtimes, {target = self}) + local shflags = self:linker("shared", sourcekind):map_flags("runtime", runtimes, {target = self}) + + -- @note the multi-argument flags must be checked as a whole, e.g. the clang runtime + -- flags on windows, `-Xclang --dependent-lib=xxx` would be broken if + -- `--dependent-lib=xxx` is checked and dropped separately + -- @see https://github.com/xmake-io/xmake/issues/7704 + local group = function (flags) + return flags and #flags > 1 and {table.wrap_lock(flags)} or flags + end + configs.cxflags = group(cxflags) + configs.ldflags = group(ldflags) + configs.shflags = group(shflags) self.sourcekinds = nil end return configs @@ -2556,6 +2614,79 @@ function _instance:_generate_build_configs(configs, opt) return configs end +-- has the given payloads? (only for the addon packages) +-- +-- @param opt the payloads to be checked, the value can be a string or a list, e.g. +-- {rules = "app", toolchains = "esp32", plugins = "monitor", +-- templates = "c/esp32.hello", modules = {"private.board", "private.flasher"}} +-- +-- @return true, or false and errors +-- +-- e.g. +-- +-- on_test(function (package) +-- assert(package:has_addon({rules = "app", toolchains = "esp32"})) +-- end) +-- +function _instance:has_addon(opt) + if not self:is_addon() then + return false, string.format("package(%s) is not an addon!", self:name()) + end + + -- the addon should be registered after installing it + local addoninfo = addon.addons()[addon.dirname(self:name())] + if not addoninfo then + return false, string.format("addon(%s) is not installed!", self:name()) + end + if opt == nil then + return true + end + + local checkers = {} + + -- the rules are namespaced, e.g. @addon/esp32-devel/app + -- + -- @note we need to reload the global rules, this addon may be installed just now + checkers.rules = function (name) + local rule = require("project/rule") + rule.clear() + return rule.rules()["@addon/" .. self:name() .. "/" .. name] ~= nil + end + + -- the toolchains are namespaced too, e.g. @addon/esp32-devel/esp32 + checkers.toolchains = function (name) + return toolchain.load("@addon/" .. self:name() .. "/" .. name) ~= nil + end + + -- the plugins and the templates are not namespaced, we get them from the addons registry, + -- the task list of this process has been loaded before installing this addon + checkers.plugins = function (name) + return table.contains(addoninfo.plugins or {}, name) + end + checkers.templates = function (name) + return table.contains(addoninfo.templates or {}, name) + end + + -- the modules are files, e.g. modules/serial.lua, modules/private/board.lua + checkers.modules = function (name) + local modulepath = path.join(self:installdir(), "modules", (name:gsub("%.", "/"))) + return os.isfile(modulepath .. ".lua") or os.isdir(modulepath) + end + + for kind, names in pairs(opt) do + local checker = checkers[kind] + if not checker then + return false, string.format("unknown addon payload(%s), it should be one of rules, toolchains, plugins, templates and modules!", kind) + end + for _, name in ipairs(table.wrap(names)) do + if not checker(name) then + return false, string.format("%s(%s) not found in the addon(%s)!", kind, name, self:name()) + end + end + end + return true +end + -- has the given c funcs? -- -- @param funcs the funcs @@ -2985,12 +3116,20 @@ end -- -- @param opt the options, e.g. {localdir = true} -- - localdir: return the local project packages directory (build/.packages) --- instead of the global directory (~/.xmake/packages) +-- instead of the global directory (~/.xmake/packages), +-- it can be overridden with `XMAKE_PKG_LOCALDIR` -- -- @return the install directory path -- function package.installdir(opt) if opt and opt.localdir then + -- the parent process passes its local directory to the sub-process which builds + -- a package, so the packages it installs locally land in the same place and are + -- not installed twice, @see https://github.com/xmake-io/xmake/issues/7716 + local localdir = os.getenv("XMAKE_PKG_LOCALDIR") + if localdir then + return path.normalize(path.absolute(localdir)) + end return path.join(config.builddir({absolute = true}), ".packages") end local installdir = package._INSTALLDIR @@ -3015,6 +3154,14 @@ function package.searchdirs() end -- load the package from the system directories +-- +-- it will be used for `add_requires("zlib", {system = true})` and the 3rd package managers, +-- e.g. add_requires("vcpkg::zlib"), add_requires("conan::zlib/1.2.11") +-- +-- @param packagename the package name, e.g. "zlib", "vcpkg::zlib", "xmake::zlib" +-- +-- @return the package instance and errors +-- function package.load_from_system(packagename) -- get package info @@ -3083,6 +3230,15 @@ function package.load_from_system(packagename) end -- load the package from the project file +-- +-- it will load the package which is defined by `package()` in the project xmake.lua, +-- and we will also try to find it from the project namespaces if it's not found directly +-- +-- @param packagename the package name, e.g. "zlib", it can be without the namespace prefix +-- @param project the project module, we need to pass it to avoid the cyclic imports +-- +-- @return the package instance and errors, it will be nil if this package is not defined in the project +-- function package.load_from_project(packagename, project) -- load packages (with cache) @@ -3111,8 +3267,20 @@ function package.load_from_project(packagename, project) end -- load the package from the package directory or package description file +-- +-- @param packagename the package name, e.g. "zlib" +-- @param packagedir the package directory, we will load `packagedir/xmake.lua`, it can be nil if `opt.packagefile` is set +-- @param opt the options +-- - packagefile: load the package from the given description file directly instead of `packagedir/xmake.lua` +-- - plat: the given platform, we need to set it to the description scope at same time, +-- e.g. add_requires("zlib~mingw", {plat = "mingw"}) +-- @see https://github.com/orgs/xmake-io/discussions/3439 +-- - arch: the given architecture, it's the same as `opt.plat` +-- - repo: the repository instance which this package belongs to +-- +-- @return the package instance and errors +-- function package.load_from_repository(packagename, packagedir, opt) - opt = opt or {} -- find the package script path @@ -3173,6 +3341,21 @@ function package.load_from_repository(packagename, packagedir, opt) return nil, string.format("%s: package(%s) not found!", scriptpath, packagename) end + -- we need set the default on_install script if it's addon package + if packageinfo:get("kind") == "addon" and not packageinfo:get("install") then + packageinfo:set("install", addon.installscript()) + end + + -- we need set the default on_install script if it's plugin package + -- @note the plugin kind is deprecated, please use the addon kind instead + if packageinfo:get("kind") == "plugin" and not packageinfo:get("install") then + -- only one code line, we can directly omit the sandbox wrapper. + local on_install = function (pkg) + os.cp("*", pkg:installdir()) + end + packageinfo:set("install", on_install) + end + package._memcache():set2("packageinfos.repository", cachekey, packageinfo) end |
