--!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 = {} -- load modules local os = require("base/os") local io = require("base/io") local path = require("base/path") local utils = require("base/utils") local table = require("base/table") local global = require("base/global") local semver = require("base/semver") local option = require("base/option") local hashset = require("base/hashset") local scopeinfo = require("base/scopeinfo") 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 toolchain = require("tool/toolchain") local compiler = require("tool/compiler") local linker = require("tool/linker") local sandbox = require("sandbox/sandbox") local config = require("project/config") local policy = require("project/policy") local platform = require("platform/platform") local platform_menu = require("platform/menu") local component = require("package/component") local scheme = require("package/scheme") local language = require("language/language") local language_menu = require("language/menu") local sandbox = require("sandbox/sandbox") local sandbox_os = require("sandbox/modules/os") local sandbox_module = require("sandbox/modules/import/core/sandbox/module") -- new an instance function _instance.new(name, info, opt) opt = opt or {} local instance = table.inherit(_instance) if name then local parts = name:split("::", {plain = true}) local managers = package._memcache():get("managers") if managers == nil and #parts == 2 then managers = hashset.new() for _, dir in ipairs(os.dirs(path.join(os.programdir(), "modules/package/manager/*"))) do managers:insert(path.filename(dir)) end package._memcache():set("managers", managers) end if #parts == 2 and managers and managers:has(parts[1]) then instance._NAME = name else instance._NAME = parts[#parts] table.remove(parts) if #parts > 0 then instance._NAMESPACE = table.concat(parts, "::") end end end instance._INFO = info instance._REPO = opt.repo instance._SCRIPTDIR = opt.scriptdir and path.absolute(opt.scriptdir) return instance end -- get memcache function _instance:memcache() local cache = self._MEMCACHE if not cache then cache = memcache.cache("core.package.package." .. tostring(self)) self._MEMCACHE = cache end return cache end -- get the package name without namespace function _instance:name() return self._NAME end -- get the namespace function _instance:namespace() return self._NAMESPACE end -- get the full name (with namespace) function _instance:fullname() local namespace = self:namespace() return namespace and namespace .. "::" .. self:name() or self:name() end -- get the display name (with namespace and ~label) function _instance:displayname() return self._DISPLAYNAME end -- set the display name function _instance:displayname_set(displayname) self._DISPLAYNAME = displayname end -- get the type: package function _instance:type() return "package" end -- get the base package function _instance:base() return self._BASE end -- get the package configuration function _instance:get(name) local value = self._INFO:get(name) if name == "configs" then -- we need to merge it, because current builtin configs always exists if self:base() then local configs_base = self:base():get("configs") if configs_base then value = table.unique(table.join(value or {}, configs_base)) end end elseif value == nil and self:base() then value = self:base():get(name) end if value ~= nil then return value end end -- set the value to the package info function _instance:set(name, ...) if self._SOURCE_INITED then -- we can use set/add to modify urls, .. in on_load() if urls have been inited. -- but we cannot init urls, ... in on_load() if it has been not inited -- -- @see https://github.com/xmake-io/xmake/issues/5148 -- https://github.com/xmake-io/xmake-repo/pull/4204 if self:_sourceset():has(name) and self:get(name) == nil then os.raise("'%s' can only be initied in on_source() or the description scope.", name) end end self._INFO:apival_set(name, ...) end -- add the value to the package info function _instance:add(name, ...) if self._SOURCE_INITED then if self:_sourceset():has(name) and self:get(name) == nil then os.raise("'%s' can only be initied in on_source() or the description scope.", name) end end self._INFO:apival_add(name, ...) end -- get the extra configuration function _instance:extraconf(name, item, key) local conf = self._INFO:extraconf(name, item, key) if conf == nil and self:base() then conf = self:base():extraconf(name, item, key) end return conf end -- set the extra configuration function _instance:extraconf_set(name, item, key, value) return self._INFO:extraconf_set(name, item, key, value) end -- get configuration source information of the given api item function _instance:sourceinfo(name, item) return self._INFO:sourceinfo(name, item) end -- get the package license function _instance:license() return self:get("license") end -- get the package description function _instance:description() return self:get("description") end -- get the platform of package function _instance:plat() if self._PLAT then return self._PLAT end if self:is_host() then return os.subhost() end local requireinfo = self:requireinfo() if requireinfo and requireinfo.plat then return requireinfo.plat end return package.targetplat() end -- get the architecture of package function _instance:arch() if self._ARCH then return self._ARCH end if self:is_host() then return os.subarch() end return self:targetarch() end -- set the package platform function _instance:plat_set(plat) self._PLAT = plat end -- set the package architecture function _instance:arch_set(arch) self._ARCH = arch end -- get the target os function _instance:targetos() local requireinfo = self:requireinfo() if requireinfo and requireinfo.targetos then return requireinfo.targetos end return config.get("target_os") or platform.os() end -- get the target architecture function _instance:targetarch() local requireinfo = self:requireinfo() if requireinfo and requireinfo.arch then return requireinfo.arch end return package.targetarch() end -- get the build mode function _instance:mode() return self:is_debug() and "debug" or "release" end -- get the repository of this package function _instance:repo() return self._REPO end -- the current platform is belong to the given platforms? function _instance:is_plat(...) local plat = self:plat() for _, v in ipairs(table.pack(...)) do if v and plat == v then return true end end end -- the current architecture is belong to the given architectures? function _instance:is_arch(...) local arch = self:arch() for _, v in ipairs(table.pack(...)) do if v and arch:find("^" .. v:gsub("%-", "%%-") .. "$") then return true end end end -- is 64bits architecture? function _instance:is_arch64() return self:is_arch(".+64.*") end -- the current platform is belong to the given target os? function _instance:is_targetos(...) local targetos = self:targetos() for _, v in ipairs(table.join(...)) do if v and targetos == v then return true end end end -- the current architecture is belong to the given target architectures? function _instance:is_targetarch(...) local targetarch = self:targetarch() for _, v in ipairs(table.pack(...)) do if v and targetarch:find("^" .. v:gsub("%-", "%%-") .. "$") then return true end end end -- get the package alias function _instance:alias() local requireinfo = self:requireinfo() if requireinfo then return requireinfo.alias end end -- get external package sources, e.g. pkgconfig::xxx, system::xxx, conan::xxx -- we can use it to improve self:fetch() for find_package function _instance:extsources() return self:get("extsources") end -- get urls function _instance:urls() return self:current_scheme():urls() end -- get urls function _instance:urls_set(urls) self:current_scheme():urls_set(urls) end -- get the alias of url, @note need raw url function _instance:url_alias(url) return self:current_scheme():url_alias(url) end -- get the version filter of url, @note need raw url function _instance:url_version(url) return self:current_scheme():url_version(url) end -- get the excludes paths of url -- @note it supports the path pattern, but it only supports for archiver. function _instance:url_excludes(url) return self:current_scheme():url_excludes(url) end -- get the includes paths of url -- @note it does not support the path pattern, and it only supports for git url now. -- @see https://github.com/xmake-io/xmake/issues/6071 function _instance:url_includes(url) return self:current_scheme():url_includes(url) end -- get the http headers of url, @note need raw url function _instance:url_http_headers(url) return self:current_scheme():url_http_headers(url) end -- install the precompiled artifacts first function _instance:use_precompiled_artifacts(artifacts_info) -- init the precompiled scheme local current_scheme = self:current_scheme() local precompiled_scheme = scheme.new("__precompiled__", {package = self}) precompiled_scheme:urls_set(table.wrap(artifacts_info.urls)) local versions_list = table.clone(current_scheme:_versions_list()) versions_list[self:version_str()] = artifacts_info.sha256 precompiled_scheme._VERSIONS_LIST = versions_list precompiled_scheme._VERSION = current_scheme._VERSION precompiled_scheme._VERSION_STR = current_scheme._VERSION_STR precompiled_scheme._TAG = current_scheme._TAG precompiled_scheme._COMMIT = current_scheme._COMMIT precompiled_scheme._BRANCH = current_scheme._BRANCH precompiled_scheme:set("install", function (package) sandbox_module.import("lib.detect.find_path") local rootdir = find_path("manifest.txt", path.join(os.curdir(), "*", "*", "*")) if not rootdir then os.raise("package(%s): manifest.txt not found when installing artifacts!", package:displayname()) end os.cp(path.join(rootdir, "*"), package:installdir(), {symlink = true}) local manifest = package:manifest_load() if not manifest then os.raise("package(%s): load manifest.txt failed when installing artifacts!", package:displayname()) end if manifest.vars then for k, v in pairs(manifest.vars) do package:set(k, v) end end if manifest.components then local vars = manifest.components.vars if vars then for component_name, component_vars in pairs(vars) do local comp = package:component(component_name) if comp then for k, v in pairs(component_vars) do comp:set(k, v) end end end end end if manifest.envs then local envs = self:_rawenvs() for k, v in pairs(manifest.envs) do envs[k] = v end end -- save the remote install directory to fix the install path in .cmake/.pc files for precompiled artifacts -- -- @see https://github.com/xmake-io/xmake/issues/2210 -- manifest.artifacts = manifest.artifacts or {} manifest.artifacts.remotedir = manifest.artifacts.installdir end) -- add the precompiled scheme table.insert(self:schemes_orderlist(), 1, precompiled_scheme) self:schemes()[precompiled_scheme:name()] = precompiled_scheme self._CURRENT_SCHEME = precompiled_scheme end -- is this package built? function _instance:is_built() return not self:is_precompiled() end -- is this package precompiled? function _instance:is_precompiled() return self:current_scheme():is_precompiled() end -- get the given dependent package function _instance:dep(name) local deps = self:deps() if deps then return deps[name] end end -- get deps function _instance:deps() return self._DEPS end -- get order deps function _instance:orderdeps() return self._ORDERDEPS end -- get plain deps function _instance:plaindeps() return self._PLAINDEPS end -- get library dep function _instance:librarydep(name, opt) local key = "librarydeps_map_" .. ((opt and opt.private) and "private" or "") local librarydeps_map = self:memcache():get(key) if not librarydeps_map then librarydeps_map = {} for _, dep in ipairs(self:librarydeps()) do librarydeps_map[dep:name()] = dep end self:memcache():set(key, librarydeps_map) end return librarydeps_map[name] end -- get library deps with correct link order function _instance:librarydeps(opt) if opt and opt.private then return self._LIBRARYDEPS_WITH_PRIVATE else return self._LIBRARYDEPS end end -- get parents function _instance:parents(packagename) local parents = self._PARENTS if parents then if packagename then return parents[packagename] else local results = self._PARENTS_PLAIN if not results then results = {} for _, parentpkgs in pairs(parents) do table.join2(results, parentpkgs) end results = table.unique(results) self._PARENTS_PLAIN = results end if #results > 0 then return results end end end end -- add parents function _instance:parents_add(...) self._PARENTS = self._PARENTS or {} for _, parent in ipairs({...}) do -- maybe multiple parents will depend on it -- @see https://github.com/xmake-io/xmake/issues/3065 local parentpkgs = self._PARENTS[parent:name()] if not parentpkgs then parentpkgs = {} self._PARENTS[parent:name()] = parentpkgs end table.insert(parentpkgs, parent) self._PARENTS_PLAIN = nil end end -- get hash of the source package for the url_alias@version_str function _instance:sourcehash(url_alias) return self:current_scheme():sourcehash(url_alias) end -- get revision(commit, tag, branch) of the url_alias@version_str, only for git url function _instance:revision(url_alias) return self:current_scheme():revision(url_alias) end -- get the package policy function _instance:policy(name) local policies = self._POLICIES if not policies then policies = self:get("policy") self._POLICIES = policies if policies then local defined_policies = policy.policies() for name, _ in pairs(policies) do if not defined_policies[name] then utils.warning("unknown policy(%s), please run `xmake l core.project.policy.policies` if you want to all policies", name) end end end end return policy.check(name, policies and policies[name]) end -- get the package kind -- -- - binary -- - toolchain (is also binary) -- - library(default) -- function _instance:kind() local kind local requireinfo = self:requireinfo() if requireinfo then kind = requireinfo.kind end if not kind then kind = self:get("kind") end return kind end -- is binary package? function _instance:is_binary() return self:kind() == "binary" or self:kind() == "toolchain" end -- is toolchain package? function _instance:is_toolchain() return self:kind() == "toolchain" end -- is library package? function _instance:is_library() return self:kind() == nil or self:kind() == "library" end -- is template package? function _instance:is_template() return self:kind() == "template" end -- is header only? function _instance:is_headeronly() return self:is_library() and self:extraconf("kind", "library", "headeronly") end -- is module only? function _instance:is_moduleonly() return self:is_library() and self:extraconf("kind", "library", "moduleonly") end -- is top level? user top requires in xmake.lua function _instance:is_toplevel() local requireinfo = self:requireinfo() return requireinfo and requireinfo.is_toplevel end -- is the system package? function _instance:is_system() return self._is_system end -- is the third-party package? e.g. brew::pcre2/libpcre2-8, conan::OpenSSL/1.0.2n@conan/stable -- we need to install and find package by third-party package manager directly -- function _instance:is_thirdparty() return self._is_thirdparty end -- is fetch only? function _instance:is_fetchonly() local project = package._project() if project and project.policy("package.fetch_only") then return true end -- only fetch script if self:get("fetch") and not self:get("install") then return true end -- only from system local requireinfo = self:requireinfo() if requireinfo and requireinfo.system then return true end return false end -- is optional package? function _instance:is_optional() local requireinfo = self:requireinfo() return requireinfo and requireinfo.optional or false end -- is private package? function _instance:is_private() local requireinfo = self:requireinfo() return requireinfo and requireinfo.private or false end -- verify sha256sum and versions? function _instance:is_verify() local requireinfo = self:requireinfo() local verify = requireinfo and requireinfo.verify if verify == nil then verify = true end return verify end -- is debug package? function _instance:is_debug() return self:config("debug") or self:config("asan") end -- should keep package source code after installing? function _instance:has_source() local project = package._project() return self:is_debug() or self:is_source_embed() or (project and project.policy("package.keep_source")) or self:policy("package.keep_source") end -- is the supported package? function _instance:is_supported() -- attempt to get the install script with the current plat/arch return self:script("install") ~= nil end -- support parallelize for installation? function _instance:is_parallelize() return self:get("parallelize") ~= false end -- is local embed source code package? -- we install directly from the local source code instead of downloading it remotely function _instance:is_source_embed() return self:get("sourcedir") and #self:urls() == 0 and self:script("install") end -- is local embed binary package? it's come from `xmake package` function _instance:is_binary_embed() return self:get("installdir") and #self:urls() == 0 and not self:script("install") and self:script("fetch") end -- is local package? -- we will use local installdir and cachedir in current project function _instance:is_local() return self._IS_LOCAL or self:is_source_embed() or self:is_binary_embed() or self:is_thirdparty() end -- is debug package? (deprecated) function _instance:debug() return self:is_debug() end -- is host package? -- -- @note It is different from not is_cross() in that users do not use host packages directly, -- they are usually used to build library packages. function _instance:is_host() local requireinfo = self:requireinfo() if requireinfo and requireinfo.host then return true end return self:is_binary() end -- is cross-compilation? function _instance:is_cross() if self:is_host() then return false end return is_cross(self:plat(), self:arch()) end -- mark it as local package function _instance:_mark_as_local(is_local) if self:is_local() ~= is_local then self._INSTALLDIR = nil self._IS_LOCAL = is_local end end -- use external includes? function _instance:use_external_includes() local external = self:requireinfo().external if external == nil then local project = package._project() if project then external = project.policy("package.include_external_headers") end end if external == nil then external = self:policy("package.include_external_headers") end -- disable -Isystem for external packages as it seems to break. e.g. assimp -- @see https://github.com/msys2/MINGW-packages/issues/10761 if external == nil and self:is_plat("mingw") and os.is_subhost("msys") then external = false end if external == nil then external = true end return external end -- get the filelock of the whole package directory function _instance:filelock() local filelock = self._FILELOCK if filelock == nil then filelock = io.openlock(path.join(self:cachedir(), "package.lock")) if not filelock then os.raise("cannot create filelock for package(%s)!", self:name()) end self._FILELOCK = filelock end return filelock end -- lock the whole package function _instance:lock(opt) if self:filelock():trylock(opt) then return true else utils.cprint("${color.warning}package(%s) is being accessed by other processes, please wait!", self:name()) end local ok, errors = self:filelock():lock(opt) if not ok then os.raise(errors) end end -- unlock the whole package function _instance:unlock() local ok, errors = self:filelock():unlock() if not ok then os.raise(errors) end end -- get the source directory function _instance:sourcedir() return self:get("sourcedir") end -- get the build directory function _instance:builddir() local builddir = self._BUILDDIR if not builddir then if self:is_local() then local name = self:name():lower():gsub("::", "_") local rootdir = path.join(config.builddir({absolute = true}), ".packages", name:sub(1, 1):lower(), name, self:version_str()) builddir = path.join(rootdir, "cache", "build_" .. hash.rand32()) else builddir = "build_" .. hash.rand32() end self._BUILDDIR = builddir end return builddir end -- get the build directory (deprecated) function _instance:buildir() utils.warning("package:buildir() has been deprecated, please use package:builddir()") return self:builddir() end -- get the cached directory of this package function _instance:cachedir() local cachedir = self._CACHEDIR if not cachedir then cachedir = self:get("cachedir") if not cachedir then -- we need to use displayname (with package id) to avoid -- multiple processes accessing it at the same time. -- -- @see https://github.com/libbpf/libbpf-bootstrap/pull/259#issuecomment-1994914188 -- -- e.g. -- -- lock elfutils#1 /home/runner/.xmake/cache/packages/2403/e/elfutils/0.189 -- lock elfutils /home/runner/.xmake/cache/packages/2403/e/elfutils/0.189 -- package(elfutils) is being accessed by other processes, please wait! -- local name = self:displayname():lower():gsub("::", "_"):gsub("#", "_") local version_str = self:version_str() -- strip invalid characters on windows, e.g. `>= <=` if version_str and os.is_host("windows") then version_str = version_str:gsub("[>=<|%*]", "") end if self:is_local() then cachedir = path.join(config.builddir({absolute = true}), ".packages", name:sub(1, 1):lower(), name, version_str, "cache") else cachedir = path.join(package.cachedir(), name:sub(1, 1):lower(), name, version_str) end end self._CACHEDIR = cachedir end return cachedir end -- get the installed directory of this package function _instance:installdir(...) local installdir = self._INSTALLDIR if not installdir then installdir = self:get("installdir") if not installdir then local name = self:name():lower():gsub("::", "_") if self:is_local() then installdir = path.join(config.builddir({absolute = true}), ".packages", 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 self._INSTALLDIR = installdir end local dirs = table.pack(...) local opt = dirs[dirs.n] if table.is_dictionary(opt) then table.remove(dirs) else opt = nil end local dir = path.join(installdir, table.unpack(dirs)) if opt and opt.readonly then return dir end if not os.isdir(dir)then os.mkdir(dir) end return dir end -- get the script directory function _instance:scriptdir() return self._SCRIPTDIR end -- get the rules directory function _instance:rulesdir() local rulesdir = self._RULESDIR if rulesdir == nil then if self:repo() == nil and self:base() then rulesdir = self:base():rulesdir() else rulesdir = path.join(self:scriptdir(), "rules") if not os.isdir(rulesdir) and self:base() then rulesdir = self:base():rulesdir() end end if rulesdir == nil or not os.isdir(rulesdir) then rulesdir = false end self._RULESDIR = rulesdir end return rulesdir or nil end -- get the references info of this package function _instance:references() local references_file = path.join(self:installdir({readonly = true}), "references.txt") if os.isfile(references_file) then local references, errors = io.load(references_file) if not references then os.raise(errors) end return references end end -- get the manifest file of this package function _instance:manifest_file() return path.join(self:installdir({readonly = true}), "manifest.txt") end -- load the manifest file of this package function _instance:manifest_load() local manifest = self._MANIFEST if not manifest then local manifest_file = self:manifest_file() if os.isfile(manifest_file) then local errors = nil manifest, errors = io.load(manifest_file) if not manifest then os.raise(errors) end self._MANIFEST = manifest end end return manifest end -- save the manifest file of this package function _instance:manifest_save() -- make manifest local manifest = {} manifest.name = self:name() manifest.license = self:license() manifest.description = self:description() manifest.version = self:version_str() manifest.kind = self:kind() manifest.plat = self:plat() manifest.arch = self:arch() manifest.mode = self:mode() manifest.configs = self:configs() manifest.envs = self:_rawenvs() -- ensure pathenvs are written deterministically manifest.pathenvs = table.to_array(self:_pathenvs():orderitems()) -- save enabled library deps if self:librarydeps() then manifest.librarydeps = {} for _, dep in ipairs(self:librarydeps()) do if dep:exists() then table.insert(manifest.librarydeps, dep:name()) end end end -- save deps if self:librarydeps() then manifest.deps = {} for _, dep in ipairs(self:librarydeps()) do manifest.deps[dep:name()] = { version = dep:version_str(), buildhash = dep:buildhash() } end end -- save global variables and component variables local vars local extras local components local apis = language.apis() for _, apiname in ipairs(table.join(apis.values, apis.paths, apis.groups)) do if apiname:startswith("package.add_") or apiname:startswith("package.set_") then local name = apiname:sub(13) local values = self:get(name) if values ~= nil then vars = vars or {} vars[name] = values local extra = self:extraconf(name) if extra then extras = extras or {} extras[name] = extra end end for _, component_name in ipairs(table.wrap(self:get("components"))) do local comp = self:component(component_name) if comp then local component_values = comp:get(name) if component_values ~= nil then components = components or {} components.vars = components.vars or {} components.vars[component_name] = components.vars[component_name] or {} components.vars[component_name][name] = component_values end end end end end manifest.vars = vars manifest.extras = extras manifest.components = components -- save repository local repo = self:repo() if repo then manifest.repo = {} manifest.repo.name = repo:name() manifest.repo.url = repo:url() manifest.repo.branch = repo:branch() manifest.repo.commit = repo:commit() end -- save artifacts information to fix the install path in .cmake/.pc files for precompiled artifacts -- -- @see https://github.com/xmake-io/xmake/issues/2210 -- manifest.artifacts = {} manifest.artifacts.installdir = self:installdir() local current_manifest = self:manifest_load() if current_manifest and current_manifest.artifacts then manifest.artifacts.remotedir = current_manifest.artifacts.remotedir end -- save manifest local ok, errors = io.save(self:manifest_file(), manifest, { orderkeys = true }) if not ok then os.raise(errors) end end -- get the source configuration set function _instance:_sourceset() local sourceset = self._SOURCESET if sourceset == nil then sourceset = hashset.of("urls", "versions", "versionfiles", "configs") self._SOURCESET = sourceset end return sourceset end -- init package source function _instance:_init_source() local inited = self._SOURCE_INITED if not inited then local on_source = self:script("source") if on_source then on_source(self) end end end -- load package function _instance:_load() self._SOURCE_INITED = true local loaded = self._LOADED if not loaded then -- load on_load script local on_load = self:script("load") if on_load then on_load(self) end -- load all components self:_load_components() -- load environments from the manifest to enable the environments of on_install() self:_load_envs() end end -- mark as loaded package function _instance:_mark_as_loaded() self._LOADED = true end -- get the raw environments function _instance:_rawenvs() local envs = self._RAWENVS if not envs then envs = {} -- add bin PATH local bindirs = self:get("bindirs") if bindirs then envs.PATH = table.wrap(bindirs) elseif self:is_binary() then envs.PATH = {"bin"} elseif os.host() == "windows" and self:is_plat("windows", "mingw") and not self:is_cross() and self:config("shared") then -- bin/*.dll for windows envs.PATH = {"bin"} end -- add LD_LIBRARY_PATH to load *.so directory if os.host() ~= "windows" and self:is_plat(os.host()) and not self:is_cross() and self:config("shared") then envs.LD_LIBRARY_PATH = {"lib"} if os.host() == "macosx" then envs.DYLD_LIBRARY_PATH = {"lib"} end end self._RAWENVS = envs end return envs end -- get path environment keys function _instance:_pathenvs() local pathenvs = self._PATHENVS if pathenvs == nil then pathenvs = hashset.from { "PATH", "LD_LIBRARY_PATH", "DYLD_LIBRARY_PATH", "PKG_CONFIG_PATH", "ACLOCAL_PATH", "CMAKE_PREFIX_PATH", "PYTHONPATH" } self._PATHENVS = pathenvs end return pathenvs end -- mark as path environments function _instance:mark_as_pathenv(name) self:_pathenvs():insert(name) end -- get the exported environments function _instance:envs() local envs = {} for name, values in pairs(self:_rawenvs()) do if self:_pathenvs():has(name) then local newvalues = {} for _, value in ipairs(values) do if path.is_absolute(value) then table.insert(newvalues, value) else table.insert(newvalues, path.normalize(path.join(self:installdir({readonly = true}), value))) end end values = newvalues end envs[name] = values end return envs end -- load the package environments from the manifest function _instance:_load_envs() local manifest = self:manifest_load() if manifest then local envs = self:_rawenvs() for name, values in pairs(manifest.envs) do envs[name] = values end end end -- load all components function _instance:_load_components() for _, component_inst in pairs(self:components()) do component_inst:_load() end end -- enter the package environments function _instance:envs_enter() local installdir = self:installdir({readonly = true}) for name, values in pairs(self:envs()) do os.addenv(name, table.unpack(table.wrap(values))) end end -- get the given environment variable function _instance:getenv(name) return self:_rawenvs()[name] end -- set the given environment variable function _instance:setenv(name, ...) self:_rawenvs()[name] = {...} end -- add the given environment variable function _instance:addenv(name, ...) self:_rawenvs()[name] = table.join(self:_rawenvs()[name] or {}, ...) end -- get the given build environment variable function _instance:build_getenv(name) return self:build_envs(true)[name] end -- set the given build environment variable function _instance:build_setenv(name, ...) self:build_envs(true)[name] = table.unwrap({...}) end -- add the given build environment variable function _instance:build_addenv(name, ...) self:build_envs(true)[name] = table.unwrap(table.join(table.wrap(self:build_envs()[name]), ...)) end -- get the build environments function _instance:build_envs(lazy_loading) local build_envs = self._BUILD_ENVS if build_envs == nil then -- lazy loading the given environment value and cache it build_envs = {} local builtin_configs = hashset.of("cflags", "cxflags", "cxxflags", "ldflags", "shflags", "asflags") setmetatable(build_envs, { __index = function (tbl, key) local result = {} local value = config.get(key) if value == nil then value = self:tool(key) end if value then table.join2(result, value) end -- we can only get the builtin config values -- https://github.com/xmake-io/xmake/issues/6897 if builtin_configs:has(key) then value = self:config(key) if value then table.join2(result, value) end end value = self:toolconfig(key) if value then table.join2(result, value) end result = table.unique(result) if #result > 0 then result = table.unwrap(result) rawset(tbl, key, result) return result end return rawget(tbl, key) end}) -- save build environments self._BUILD_ENVS = build_envs end -- force to load all values if need if not lazy_loading then for _, opt in ipairs(table.join(language_menu.options("config"), platform_menu.options("config"))) do local optname = opt[2] if type(optname) == "string" then -- we only need to index it to force load it's value local value = build_envs[optname] end end end return build_envs end -- get runtimes function _instance:runtimes() local runtimes = self:memcache():get("runtimes") if runtimes == nil then runtimes = self:config("runtimes") if runtimes then local runtimes_current = runtimes:split(",", {plain = true}) runtimes = table.unwrap(runtimes_current) end runtimes = runtimes or false self:memcache():set("runtimes", runtimes) end return runtimes or nil end -- has the given runtime for the current toolchains? function _instance:has_runtime(...) local runtimes_set = self:memcache():get("runtimes_set") if runtimes_set == nil then runtimes_set = hashset.from(table.wrap(self:runtimes())) self:memcache():set("runtimes_set", runtimes_set) end for _, v in ipairs(table.pack(...)) do if runtimes_set:has(v) then return true end end end -- get the given toolchain function _instance:toolchain(name) local toolchains_map = self:memcache():get("toolchains_map") if toolchains_map == nil then toolchains_map = {} local toolchains = self:toolchains() if toolchains then for _, toolchain_inst in ipairs(toolchains) do toolchains_map[toolchain_inst:name()] = toolchain_inst end end self:memcache():set("toolchains_map", toolchains_map) end if not toolchains_map[name] then toolchains_map[name] = toolchain.load(name, {plat = self:plat(), arch = self:arch()}) end return toolchains_map[name] end -- get toolchains function _instance:toolchains() local toolchains = self._TOOLCHAINS if toolchains == nil then local project = package._project() for _, name in ipairs(table.wrap(self:config("toolchains"))) do local toolchain_opt = project and project.extraconf("target.toolchains", name) or {} toolchain_opt.plat = self:plat() toolchain_opt.arch = self:arch() toolchain_opt.namespace = self:namespace() local toolchain_inst, errors = toolchain.load(name, toolchain_opt) if not toolchain_inst and project then toolchain_inst = project.toolchain(name, toolchain_opt) end if not toolchain_inst then os.raise(errors) end toolchains = toolchains or {} table.insert(toolchains, toolchain_inst) end self._TOOLCHAINS = toolchains or false end return toolchains or nil end -- get the program and name of the given tool kind function _instance:tool(toolkind) if self:toolchains() then local cachekey = "package_" .. tostring(self) return toolchain.tool(self:toolchains(), toolkind, {cachekey = cachekey, plat = self:plat(), arch = self:arch()}) else return platform.tool(toolkind, self:plat(), self:arch(), {host = self:is_host()}) end end -- get tool configuration from the toolchains function _instance:toolconfig(name) if self:toolchains() then local cachekey = "package_" .. tostring(self) return toolchain.toolconfig(self:toolchains(), name, {cachekey = cachekey, plat = self:plat(), arch = self:arch()}) else return platform.toolconfig(name, self:plat(), self:arch(), {host = self:is_host()}) end end -- get the package compiler function _instance:compiler(sourcekind) local compilerinst = self:memcache():get2("compiler", sourcekind) if not compilerinst then if not sourcekind then os.raise("please pass sourcekind to the first argument of package:compiler(), e.g. cc, cxx, as") end local instance, errors = compiler.load(sourcekind, self) if not instance then os.raise(errors) end compilerinst = instance self:memcache():set2("compiler", sourcekind, compilerinst) end return compilerinst end -- get the package linker function _instance:linker(targetkind, sourcekinds) local linkerinst = self:memcache():get3("linker", targetkind, sourcekinds) if not linkerinst then if not sourcekinds then os.raise("please pass sourcekinds to the second argument of package:linker(), e.g. cc, cxx, as") end local instance, errors = linker.load(targetkind, sourcekinds, self) if not instance then os.raise(errors) end linkerinst = instance self:memcache():set3("linker", targetkind, sourcekinds, linkerinst) end return linkerinst end -- has the given tool for the current package? -- -- e.g. -- -- if package:has_tool("cc", "clang", "gcc") then -- ... -- end function _instance:has_tool(toolkind, ...) local target_utils = package._target_utils if target_utils == nil then target_utils = sandbox_module.import("private.utils.target", {anonymous = true}) package._target_utils = target_utils end local _, toolname = self:tool(toolkind) return target_utils.has_tool(toolname, table.pack(...)) end -- get the user private data function _instance:data(name) return self._DATA and self._DATA[name] or nil end -- set user private data function _instance:data_set(name, data) self._DATA = self._DATA or {} self._DATA[name] = data end -- add user private data function _instance:data_add(name, data) self._DATA = self._DATA or {} self._DATA[name] = table.unwrap(table.join(self._DATA[name] or {}, data)) end -- get the downloaded original file function _instance:originfile() return self._ORIGINFILE end -- set the downloaded original file function _instance:originfile_set(filepath) self._ORIGINFILE = filepath end -- get versions list function _instance:_versions_list() return self:current_scheme():_versions_list() end -- get versions function _instance:versions() return self:current_scheme():versions() end -- get the version function _instance:version() return self:current_scheme():version() end -- get the version string function _instance:version_str() return self:current_scheme():version_str() end -- set the version, source: branch, tag, version function _instance:version_set(version, source) self:current_scheme():version_set(version, source) end -- get branch version function _instance:branch() return self:current_scheme():branch() end -- get tag version function _instance:tag() return self:current_scheme():tag() end -- get commit version function _instance:commit() return self:current_scheme():commit() end -- is git ref? function _instance:gitref() return self:current_scheme():gitref() end -- get the require info function _instance:requireinfo() return self._REQUIREINFO end -- set the require info function _instance:requireinfo_set(requireinfo) self._REQUIREINFO = requireinfo end -- get label function _instance:label() local requireinfo = self:requireinfo() return requireinfo and requireinfo.label end -- invalidate configs function _instance:_invalidate_configs() self._CONFIGS = nil self._CONFIGS_FOR_BUILDHASH = nil end -- get the given configuration value of package function _instance:config(name) local value local configs = self:configs() if configs then value = configs[name] -- vs_runtime is deprecated now if name == "vs_runtime" then local runtimes = configs.runtimes if runtimes then for _, item in ipairs(runtimes:split(",")) do if item:startswith("MT") or item:startswith("MD") then value = item break end end end utils.warning("please use package:runtimes() or package:has_runtime() instead of package:config(\"vs_runtime\")") end end return value end -- set configuration value function _instance:config_set(name, value) local configs = self:configs() if configs then configs[name] = value end end -- get the configurations of package function _instance:configs() local configs = self._CONFIGS if configs == nil then local configs_defined = self:get("configs") if configs_defined then configs = {} local requireinfo = self:requireinfo() local configs_required = requireinfo and requireinfo.configs or {} local configs_overrided = requireinfo and requireinfo.configs_overrided or {} for _, name in ipairs(table.wrap(configs_defined)) do local value = configs_overrided[name] or configs_required[name] if value == nil then value = self:extraconf("configs", name, "default") -- support for the deprecated vs_runtime in add_configs if name == "runtimes" and value == nil then value = self:extraconf("configs", "vs_runtime", "default") end end configs[name] = value end else configs = false end self._CONFIGS = configs end return configs and configs or nil end -- get the given configuration value of package for buildhash function _instance:_config_for_buildhash(name) local value local configs = self:_configs_for_buildhash() if configs then value = configs[name] end return value end -- get the configurations of package for buildhash -- @note on_test still need these configs function _instance:_configs_for_buildhash() local configs = self._CONFIGS_FOR_BUILDHASH if configs == nil then local configs_defined = self:get("configs") if configs_defined then configs = {} local requireinfo = self:requireinfo() local configs_required = requireinfo and requireinfo.configs or {} local configs_overrided = requireinfo and requireinfo.configs_overrided or {} local ignored_configs_for_buildhash = hashset.from(requireinfo and requireinfo.ignored_configs_for_buildhash or {}) for _, name in ipairs(table.wrap(configs_defined)) do if not ignored_configs_for_buildhash:has(name) then local value = configs_overrided[name] or configs_required[name] if value == nil then value = self:extraconf("configs", name, "default") -- support for the deprecated vs_runtime in add_configs if name == "runtimes" and value == nil then value = self:extraconf("configs", "vs_runtime", "default") end end configs[name] = value end end else configs = false end self._CONFIGS_FOR_BUILDHASH = configs end return configs and configs or nil end -- compute the build hash function _instance:_compute_buildhash() self._BUILDHASH_PREPARED = true self:buildhash() end -- hash.strhash128 has been switched to xxhash. -- For compatibility, the old hash algorithm is still used here. function _instance:_strhash128(str) return hash.uuid4(str):replace("-", "", {plain = true}):lower() end -- get the build hash function _instance:buildhash() local buildhash = self._BUILDHASH if buildhash == nil then if not self._BUILDHASH_PREPARED then os.raise("package:buildhash() must be called after loading package") end local function _get_buildhash(configs, opt) opt = opt or {} local str = self:plat() .. self:arch() local label = self:label() if label then str = str .. label end if configs then -- with old vs_runtime configs -- https://github.com/xmake-io/xmake/issues/4477 if opt.vs_runtime then configs = table.clone(configs) configs.vs_runtime = configs.runtimes configs.runtimes = nil end -- since luajit v2.1, the key order of the table is random and undefined. -- We cannot directly deserialize the table, so the result may be different each time local configs_order = {} for k, v in pairs(table.wrap(configs)) do if type(v) == "table" then v = string.serialize(v, {strip = true, indent = false, orderkeys = true}) end table.insert(configs_order, k .. "=" .. tostring(v)) end table.sort(configs_order) -- we need to be compatible with the hash value string for the previous luajit version local configs_str = string.serialize(configs_order, true) configs_str = configs_str:gsub("\"", "") str = str .. configs_str end if opt.sourcehash ~= false then local sourcehashs = hashset.new() for _, url in ipairs(self:urls()) do local url_alias = self:url_alias(url) local sourcehash = self:sourcehash(url_alias) if sourcehash then sourcehashs:insert(sourcehash) end end if not sourcehashs:empty() then local hashs = sourcehashs:to_array() table.sort(hashs) str = str .. "_" .. table.concat(hashs, "_") end end local toolchains = self:_config_for_buildhash("toolchains") if opt.toolchains ~= false and toolchains then toolchains = table.copy(table.wrap(toolchains)) table.sort(toolchains) str = str .. "_" .. table.concat(toolchains, "_") end return self:_strhash128(str) end local function _get_installdir(...) local name = self:name():lower():gsub("::", "_") local dir = path.join(package.installdir(), name:sub(1, 1):lower(), name) if self:version_str() then dir = path.join(dir, self:version_str()) end return path.join(dir, ...) end -- we need to be compatible with the hash value string for the previous xmake version -- without builtin pic configuration (< 2.5.1). if self:_config_for_buildhash("pic") then local configs = table.copy(self:_configs_for_buildhash()) configs.pic = nil buildhash = _get_buildhash(configs, {sourcehash = false, toolchains = false}) if not os.isdir(_get_installdir(buildhash)) then buildhash = nil end end -- we need to be compatible with the hash value string for the previous xmake version -- without sourcehash (< 2.5.2) if not buildhash then buildhash = _get_buildhash(self:_configs_for_buildhash(), {sourcehash = false, toolchains = false}) if not os.isdir(_get_installdir(buildhash)) then buildhash = nil end end -- we need to be compatible with the previous xmake version -- without toolchains (< 2.6.4) if not buildhash then buildhash = _get_buildhash(self:_configs_for_buildhash(), {toolchains = false}) if not os.isdir(_get_installdir(buildhash)) then buildhash = nil end end -- we need to be compatible with the previous xmake version -- with deprecated vs_runtime (< 2.8.7) -- @see https://github.com/xmake-io/xmake/issues/4477 if not buildhash then buildhash = _get_buildhash(self:_configs_for_buildhash(), {vs_runtime = true}) if not os.isdir(_get_installdir(buildhash)) then buildhash = nil end end -- get build hash for current version if not buildhash then buildhash = _get_buildhash(self:_configs_for_buildhash()) end self._BUILDHASH = buildhash end return buildhash end -- get the group name function _instance:group() local requireinfo = self:requireinfo() if requireinfo then return requireinfo.group end end -- get xxx_script function _instance:script(name, generic) -- get script local script = self:current_scheme():get(name) or self:get(name) local result = select_script(script, {plat = self:plat(), arch = self:arch()}) or generic -- imports some modules first if result and result ~= generic then local scope = getfenv(result) if scope then for _, modulename in ipairs(table.wrap(self:get("imports"))) do scope[sandbox_module.name(modulename)] = sandbox_module.import(modulename, { rootdir = self:scriptdir(), anonymous = true}) end end end return result end -- do fetch tool function _instance:_fetch_tool(opt) opt = opt or {} local fetchinfo local on_fetch = self:script("fetch") if on_fetch then fetchinfo = on_fetch(self, {force = opt.force, system = opt.system, require_version = opt.require_version}) if fetchinfo and opt.require_version and opt.require_version:find(".", 1, true) then local version = type(fetchinfo) == "table" and fetchinfo.version if not (version and (version == opt.require_version or semver.satisfies(version, opt.require_version))) then fetchinfo = nil end end end -- we can disable to fallback fetch if on_fetch return false if fetchinfo == nil then self._find_tool = self._find_tool or sandbox_module.import("lib.detect.find_tool", {anonymous = true}) if opt.system then local fetchnames = {} if not self:is_thirdparty() then table.join2(fetchnames, self:extsources()) end table.insert(fetchnames, self:name()) for _, fetchname in ipairs(fetchnames) do fetchinfo = self:find_tool(fetchname, opt) if fetchinfo then break end end else fetchinfo = self:find_tool(self:name(), {require_version = opt.require_version, cachekey = "fetch_package_xmake", norun = true, -- we don't need to run it to check for xmake/packages, @see https://github.com/xmake-io/xmake-repo/issues/66 system = false, -- we only find it from xmake/packages, @see https://github.com/xmake-io/xmake-repo/pull/2085 force = opt.force}) -- may be toolset, not single tool if not fetchinfo then fetchinfo = self:manifest_load() end end end return fetchinfo or nil end -- do fetch library -- -- @param opt the options, e.g. {force, system, external, require_version} -- function _instance:_fetch_library(opt) opt = opt or {} local fetchinfo local on_fetch = self:script("fetch") if on_fetch then -- we cannot fetch it from system if it's cross-compilation package if not opt.system or (opt.system and not self:is_cross()) then fetchinfo = on_fetch(self, {force = opt.force, system = opt.system, external = opt.external, require_version = opt.require_version}) end if fetchinfo and opt.require_version and opt.require_version:find(".", 1, true) then local version = fetchinfo.version if not (version and (version == opt.require_version or semver.satisfies(version, opt.require_version))) then fetchinfo = nil end end if fetchinfo then local components_base = fetchinfo.components and fetchinfo.components.__base if opt.external then fetchinfo.sysincludedirs = fetchinfo.sysincludedirs or fetchinfo.includedirs fetchinfo.includedirs = nil if components_base then components_base.sysincludedirs = components_base.sysincludedirs or components_base.includedirs components_base.includedirs = nil end else fetchinfo.includedirs = fetchinfo.includedirs or fetchinfo.sysincludedirs fetchinfo.sysincludedirs = nil if components_base then components_base.includedirs = components_base.includedirs or components_base.sysincludedirs components_base.sysincludedirs = nil end end local package_utils = sandbox_module.import("private.utils.package", {anonymous = true}) package_utils.fetchinfo_set_concat(fetchinfo) end if fetchinfo and option.get("verbose") then local reponame = self:repo() and self:repo():name() or "" utils.cprint("checking for %s::%s ... ${color.success}%s %s", reponame, self:name(), self:name(), fetchinfo.version and fetchinfo.version or "") end end if fetchinfo == nil then if opt.system then local fetchnames = {} if not self:is_thirdparty() then table.join2(fetchnames, self:extsources()) end table.insert(fetchnames, self:name()) for _, fetchname in ipairs(fetchnames) do local components_extsources = {} for name, comp in pairs(self:components()) do for _, extsource in ipairs(table.wrap(comp:get("extsources"))) do local extsource_info = extsource:split("::") if fetchname:split("::")[1] == extsource_info[1] then components_extsources[name] = extsource_info[2] break end end end fetchinfo = self:find_package(fetchname, table.join(opt, {components_extsources = components_extsources})) if fetchinfo then break end end else fetchinfo = self:find_package("xmake::" .. self:name(), { require_version = opt.require_version, cachekey = "fetch_package_xmake", external = opt.external, force = opt.force}) end end return fetchinfo or nil end -- find tool function _instance:find_tool(name, opt) opt = opt or {} self._find_tool = self._find_tool or sandbox_module.import("lib.detect.find_tool", {anonymous = true}) return self._find_tool(name, {cachekey = opt.cachekey or "fetch_package_system", installdir = self:installdir({readonly = true}), bindirs = self:get("bindirs"), version = true, -- we alway check version require_version = opt.require_version, check = opt.check, command = opt.command, parse = opt.parse, norun = opt.norun, system = opt.system, force = opt.force}) end -- find package function _instance:find_package(name, opt) opt = opt or {} self._find_package = self._find_package or sandbox_module.import("lib.detect.find_package", {anonymous = true}) local system = opt.system if system == nil and not name:startswith("xmake::") then system = true -- find system package by default end local configs = table.clone(self:configs()) or {} if opt.configs then table.join2(configs, opt.configs) end if configs.runtimes then configs.runtimes = self:runtimes() end return self._find_package(name, { force = opt.force, installdir = self:installdir({readonly = true}), bindirs = self:get("bindirs"), version = true, -- we alway check version require_version = opt.require_version, mode = self:mode(), plat = self:plat(), arch = self:arch(), configs = configs, components = self:components_orderlist(), components_extsources = opt.components_extsources, buildhash = self:buildhash(), -- for xmake package or 3rd package manager, e.g. go:: .. cachekey = opt.cachekey or "fetch_package_system", external = opt.external, system = system, -- the following options is only for system::find_package sourcekind = opt.sourcekind, package = self, funcs = opt.funcs, snippets = opt.snippets, includes = opt.includes}) end -- fetch the local package info -- -- @param opt the fetch option, e.g. {force = true, external = false, system = true} -- -- @return {packageinfo}, fetchfrom (e.g. xmake/system) -- function _instance:fetch(opt) -- init options opt = opt or {} -- attempt to get it from cache local fetchinfo = self._FETCHINFO local usecache = opt.external == nil and opt.system == nil if not opt.force and usecache and fetchinfo then return fetchinfo end -- fetch the require version local require_ver = opt.version or self:requireinfo().version if not self:is_thirdparty() and not require_ver:find('.', 1, true) then -- strip branch version only system package require_ver = nil end -- nil: find xmake or system packages -- true: only find system package -- false: only find xmake packages local system = opt.system if system == nil then system = self:requireinfo().system end if self:is_thirdparty() then -- we need ignore `{system = true/false}` argument if be 3rd package -- @see https://github.com/xmake-io/xmake/issues/726 system = nil end -- install only? local project = package._project() if project and project.policy("package.install_only") then system = false end -- use sysincludedirs/-isystem instead of -I? local external if opt.external ~= nil then external = opt.external else external = self:use_external_includes() end -- always install to the local project directory? -- @see https://github.com/xmake-io/xmake/pull/4376 local install_locally if project and project.policy("package.install_locally") then install_locally = true end if install_locally == nil and self:policy("package.install_locally") then install_locally = true end if not self:is_local() and install_locally and system ~= true then local has_global = os.isfile(self:manifest_file()) self:_mark_as_local(true) if has_global and not os.isfile(self:manifest_file()) then self:_mark_as_local(false) end end -- fetch binary tool? fetchinfo = nil local is_system = nil if self:is_binary() then -- only fetch it from the xmake repository first if not fetchinfo and system ~= true and not self:is_thirdparty() then fetchinfo = self:_fetch_tool({require_version = self:version_str(), force = opt.force}) if fetchinfo then is_system = self._is_system end end -- fetch it from the system directories (disabled for cross-compilation) if not fetchinfo and system ~= false and not self:is_cross() then fetchinfo = self:_fetch_tool({system = true, require_version = require_ver, force = opt.force}) if fetchinfo then is_system = true end end else -- only fetch it from the xmake repository first if not fetchinfo and system ~= true and not self:is_thirdparty() then fetchinfo = self:_fetch_library({require_version = self:version_str(), external = external, force = opt.force}) if fetchinfo then is_system = self._is_system end end -- fetch it from the system and external package sources if not fetchinfo and system ~= false then fetchinfo = self:_fetch_library({system = true, require_version = require_ver, external = external, force = opt.force}) if fetchinfo then is_system = true end end end -- save to cache if usecache then self._FETCHINFO = fetchinfo end -- we need to update the real version if it's system package -- @see https://github.com/xmake-io/xmake/issues/3333 if is_system and fetchinfo and fetchinfo.version then local fetch_version = semver.new(fetchinfo.version) if fetch_version then self._VERSION = fetch_version self._VERSION_STR = fetchinfo.version end end -- mark as system package? if is_system ~= nil then self._is_system = is_system end return fetchinfo end -- exists this package? function _instance:exists() return self._FETCHINFO ~= nil end -- fetch library dependencies function _instance:fetch_librarydeps() local fetchinfo = self:fetch() if not fetchinfo then return end fetchinfo = table.clone(fetchinfo, 3) -- avoid the cached fetchinfo be modified local librarydeps = self:librarydeps() if librarydeps then for _, dep in ipairs(librarydeps) do local depinfo = dep:fetch() if depinfo then for name, values in pairs(depinfo) do if name ~= "license" and name ~= "version" then fetchinfo[name] = table.wrap(fetchinfo[name]) table.join2(fetchinfo[name], values) end end end end end if fetchinfo then for name, values in pairs(fetchinfo) do if name == "links" or name == "syslinks" or name == "frameworks" then fetchinfo[name] = table.unwrap(table.reverse_unique(table.wrap(values))) else fetchinfo[name] = table.unwrap(table.unique(table.wrap(values))) end end end return fetchinfo end -- get the patches of the current version -- -- @code -- add_patches("6.7.6", "https://cdn.kernel.org/pub/linux/kernel/v6.x/patch-6.7.6.xz", -- "a394326aa325f8a930a4ce33c69ba7b8b454aef1107a4d3c2a8ae12908615fc4", {reverse = true}) -- @endcode -- function _instance:patches() return self:current_scheme():patches() end -- get the resources of the current version function _instance:resources() return self:current_scheme():resources() end -- get the the given resource function _instance:resource(name) local resources = self:resources() return resources and resources[name] or nil end -- get the the given resource file function _instance:resourcefile(name) local resource = self:resource(name) if resource and resource.url then return path.join(self:cachedir(), "resources", name, (path.filename(resource.url):gsub("%?.+$", ""))) end end -- get the the given resource directory function _instance:resourcedir(name) local resource = self:resource(name) if resource and resource.url then local resourceurl = resource.url local resourcedir = path.join(self:cachedir(), "resources", name, (path.filename(resourceurl):gsub("%?.+$", ""))) if not resourceurl:startswith("git://") and not resourceurl:endswith(".git") then resourcedir = resourcedir .. ".dir" end return resourcedir end end -- get the given package component function _instance:component(name) return self:components()[name] end -- get package components -- -- .e.g. add_components("graphics", "windows") -- function _instance:components() local components = self._COMPONENTS if not components then components = {} for _, name in ipairs(table.wrap(self:get("components"))) do components[name] = component.new(name, {package = self}) end self._COMPONENTS = components end return components end -- get package dependencies of components -- -- @see https://github.com/xmake-io/xmake/issues/2636#issuecomment-1284787681 -- -- @code -- add_components("graphics", {deps = "window"}) -- @endcode -- -- or -- -- @code -- on_component(function (package, component)) -- component:add("deps", "window") -- end) -- @endcode -- function _instance:components_deps() local components_deps = self._COMPONENTS_DEPS if not components_deps then components_deps = {} for _, name in ipairs(table.wrap(self:get("components"))) do components_deps[name] = self:extraconf("components", name, "deps") or self:component(name):get("deps") end self._COMPONENTS_DEPS = components_deps end return components_deps end -- get default components -- -- @see https://github.com/xmake-io/xmake/issues/3164 -- -- @code -- add_components("graphics", {default = true}) -- @endcode -- -- or -- -- @code -- on_component(function (package, component)) -- component:set("default", true) -- end) -- @endcode -- function _instance:components_default() local components_default = self._COMPONENTS_DEFAULT if not components_default then for _, name in ipairs(table.wrap(self:get("components"))) do if self:extraconf("components", name, "default") or self:component(name):get("default") then components_default = components_default or {} table.insert(components_default, name) end end self._COMPONENTS_DEFAULT = components_default or false end return components_default or nil end -- get package components list with dependencies order function _instance:components_orderlist() local components_orderlist = self._COMPONENTS_ORDERLIST if not components_orderlist then components_orderlist = {} for _, name in ipairs(table.wrap(self:get("components"))) do table.insert(components_orderlist, name) table.join2(components_orderlist, self:_sort_componentdeps(name)) end components_orderlist = table.reverse_unique(components_orderlist) self._COMPONENTS_ORDERLIST = components_orderlist end return components_orderlist 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 -- get the given package scheme function _instance:scheme(name) if not name then os.raise("scheme name is required") end local scheme = self:schemes()[name] if not scheme then os.raise("scheme %s not found", name) end return scheme end -- set current scheme function _instance:current_scheme_set(scheme) self._CURRENT_SCHEME = scheme end -- get current scheme function _instance:current_scheme() if self._CURRENT_SCHEME == nil then local schemes_orderlist = self:schemes_orderlist() if #schemes_orderlist > 0 then self._CURRENT_SCHEME = schemes_orderlist[1] end end return self._CURRENT_SCHEME end -- get package schemes -- -- .e.g. add_schemes("binary", "source") -- function _instance:schemes() local schemes = self._SCHEMES if not schemes then schemes = {} for _, scheme in ipairs(self:schemes_orderlist()) do schemes[scheme:name()] = scheme end self._SCHEMES = schemes end return schemes end -- get package schemes list (ordered) function _instance:schemes_orderlist() local schemes_orderlist = self._SCHEMES_ORDERLIST if not schemes_orderlist then schemes_orderlist = {} local scheme_names = table.wrap(self:get("schemes")) if #scheme_names == 0 then scheme_names = {"__default__"} end for _, name in ipairs(scheme_names) do table.insert(schemes_orderlist, scheme.new(name, {package = self})) end self._SCHEMES_ORDERLIST = schemes_orderlist end return schemes_orderlist end -- prepare to install the given scheme function _instance:prepare_install_scheme(scheme) self:current_scheme_set(scheme) -- reset manifest to trigger reinstalling self._MANIFEST = nil end -- generate lto configs function _instance:_generate_lto_configs(sourcekind) -- add cflags local configs = {} if sourcekind then local _, cc = self:tool(sourcekind) local cflag = sourcekind == "cxx" and "cxxflags" or "cflags" if cc == "cl" then configs[cflag] = "-GL" elseif cc == "clang" or cc == "clangxx" or cc == "clang_cl" then configs[cflag] = "-flto=thin" elseif cc == "gcc" or cc == "gxx" then configs[cflag] = "-flto" end end -- add ldflags and shflags local _, ld = self:tool("ld") if ld == "link" then configs.ldflags = "-LTCG" configs.shflags = "-LTCG" elseif ld == "clang" or ld == "clangxx" then configs.ldflags = "-flto=thin" configs.shflags = "-flto=thin" elseif ld == "gcc" or ld == "gxx" then configs.ldflags = "-flto" configs.shflags = "-flto" end return configs end -- generate sanitizer configs function _instance:_generate_sanitizer_configs(checkmode, sourcekind) local toolchain_utils = sandbox_module.import("private.utils.toolchain", {anonymous = true}) return toolchain_utils.get_sanitizer_flags(self, {checkmode = checkmode, sourcekind = sourcekind}) end -- generate building configs for has_xxx/check_xxx function _instance:_generate_build_configs(configs, opt) opt = opt or {} configs = table.join(self:fetch_librarydeps() or {}, configs) -- since we are ignoring the runtimes of the headeronly library, -- we can only get the runtimes from the dependency library to detect the link. local runtimes = self:runtimes() if self:is_headeronly() and not runtimes and self:librarydeps() then for _, dep in ipairs(self:librarydeps()) do if dep:is_plat("windows") and dep:runtimes() then runtimes = dep:runtimes() break end end end if runtimes then -- @note we need to patch package:sourcekinds(), because it wiil be called nf_runtime for gcc/clang local sourcekind = opt.sourcekind or "cxx" self.sourcekinds = function (self) return sourcekind end local compiler = self:compiler(sourcekind) local cxflags = compiler:map_flags("runtime", runtimes, {target = self}) configs.cxflags = table.wrap(configs.cxflags) table.insert(configs.cxflags, cxflags) local ldflags = self:linker("binary", sourcekind):map_flags("runtime", runtimes, {target = self}) configs.ldflags = table.wrap(configs.ldflags) table.insert(configs.ldflags, ldflags) local shflags = self:linker("shared", sourcekind):map_flags("runtime", runtimes, {target = self}) configs.shflags = table.wrap(configs.shflags) table.insert(configs.shflags, shflags) self.sourcekinds = nil end if self:config("lto") then local configs_lto = self:_generate_lto_configs(opt.sourcekind or "cxx") if configs_lto then for k, v in pairs(configs_lto) do configs[k] = table.wrap(configs[k] or {}) table.join2(configs[k], v) end end end if self:config("asan") then local configs_asan = self:_generate_sanitizer_configs("address", opt.sourcekind or "cxx") if configs_asan then for k, v in pairs(configs_asan) do configs[k] = table.wrap(configs[k] or {}) table.join2(configs[k], v) end end end -- enable exceptions for msvc by default if opt.sourcekind == "cxx" and configs.exceptions == nil and self:has_tool("cxx", "cl") then configs.exceptions = "cxx" end -- pass user flags to on_test, because some flags need be passed to ldflags in on_test -- e.g. add_requireconfs("**", {configs = {cxflags = "/fsanitize=address", ldflags = "/fsanitize=address"}}) -- -- @see https://github.com/xmake-io/xmake/issues/4046 -- for name, flags in pairs(self:configs()) do if name:endswith("flags") and self:extraconf("configs", name, "builtin") then configs[name] = table.wrap(configs[name] or {}) table.join2(configs[name], flags) end end if configs and (configs.ldflags or configs.shflags) then configs.force = {ldflags = configs.ldflags, shflags = configs.shflags} configs.ldflags = nil configs.shflags = nil end -- check links for library if self:is_library() and not self:is_headeronly() and not self:is_moduleonly() and self:exists() then -- we need to skip it if it's in on_check, @see https://github.com/xmake-io/xmake-repo/pull/4834 local links = table.wrap(configs.links) local ldflags = table.wrap(configs.ldflags) local frameworks = table.wrap(configs.frameworks) if #links == 0 and #ldflags == 0 and #frameworks == 0 then os.raise("package(%s): links not found!", self:name()) end end return configs end -- has the given c funcs? -- -- @param funcs the funcs -- @param opt the argument options, e.g. {includes = "xxx.h", configs = {defines = ""}} -- -- @return true or false, errors -- function _instance:has_cfuncs(funcs, opt) opt = opt or {} opt.target = self opt.configs = self:_generate_build_configs(opt.configs, {sourcekind = "cc"}) return sandbox_module.import("lib.detect.has_cfuncs", {anonymous = true})(funcs, opt) end -- has the given c++ funcs? -- -- @param funcs the funcs -- @param opt the argument options, e.g. {includes = "xxx.h", configs = {defines = ""}} -- -- @return true or false, errors -- function _instance:has_cxxfuncs(funcs, opt) opt = opt or {} opt.target = self opt.configs = self:_generate_build_configs(opt.configs, {sourcekind = "cxx"}) return sandbox_module.import("lib.detect.has_cxxfuncs", {anonymous = true})(funcs, opt) end -- has the given c types? -- -- @param types the types -- @param opt the argument options, e.g. {configs = {defines = ""}} -- -- @return true or false, errors -- function _instance:has_ctypes(types, opt) opt = opt or {} opt.target = self opt.configs = self:_generate_build_configs(opt.configs, {sourcekind = "cc"}) return sandbox_module.import("lib.detect.has_ctypes", {anonymous = true})(types, opt) end -- has the given c++ types? -- -- @param types the types -- @param opt the argument options, e.g. {configs = {defines = ""}} -- -- @return true or false, errors -- function _instance:has_cxxtypes(types, opt) opt = opt or {} opt.target = self opt.configs = self:_generate_build_configs(opt.configs, {sourcekind = "cxx"}) return sandbox_module.import("lib.detect.has_cxxtypes", {anonymous = true})(types, opt) end -- has the given c includes? -- -- @param includes the includes -- @param opt the argument options, e.g. {configs = {defines = ""}} -- -- @return true or false, errors -- function _instance:has_cincludes(includes, opt) opt = opt or {} opt.target = self opt.configs = self:_generate_build_configs(opt.configs, {sourcekind = "cc"}) return sandbox_module.import("lib.detect.has_cincludes", {anonymous = true})(includes, opt) end -- has the given c++ includes? -- -- @param includes the includes -- @param opt the argument options, e.g. {configs = {defines = ""}} -- -- @return true or false, errors -- function _instance:has_cxxincludes(includes, opt) opt = opt or {} opt.target = self opt.configs = self:_generate_build_configs(opt.configs, {sourcekind = "cxx"}) return sandbox_module.import("lib.detect.has_cxxincludes", {anonymous = true})(includes, opt) end -- has the given c flags? -- -- @param flags the flags -- @param opt the argument options, e.g. { flagskey = "xxx" } -- -- @return true or false, errors -- function _instance:has_cflags(flags, opt) local compinst = self:compiler("cc") return compinst:has_flags(flags, "cflags", opt) end -- has the given c++ flags? -- -- @param flags the flags -- @param opt the argument options, e.g. { flagskey = "xxx" } -- -- @return true or false, errors -- function _instance:has_cxxflags(flags, opt) local compinst = self:compiler("cxx") return compinst:has_flags(flags, "cxxflags", opt) end -- has the given features? -- -- @param features the features, e.g. {"c_static_assert", "cxx_constexpr"} -- @param opt the argument options, e.g. {flags = ""} -- -- @return true or false, errors -- function _instance:has_features(features, opt) opt = opt or {} opt.target = self return sandbox_module.import("core.tool.compiler", {anonymous = true}).has_features(features, opt) end -- check the size of type -- -- @param typename the typename -- @param opt the argument options, e.g. {includes = "xxx.h", configs = {defines = ""}} -- -- @return the type size -- function _instance:check_sizeof(typename, opt) opt = opt or {} opt.target = self return sandbox_module.import("lib.detect.check_sizeof", {anonymous = true})(typename, opt) end -- check the given c snippets? -- -- @param snippets the snippets -- @param opt the argument options, e.g. {includes = "xxx.h", configs = {defines = ""}} -- -- @return true or false, errors -- function _instance:check_csnippets(snippets, opt) opt = opt or {} opt.target = self opt.configs = self:_generate_build_configs(opt.configs, {sourcekind = "cc"}) return sandbox_module.import("lib.detect.check_csnippets", {anonymous = true})(snippets, opt) end -- check the given c++ snippets? -- -- @param snippets the snippets -- @param opt the argument options, e.g. {includes = "xxx.h", configs = {defines = ""}} -- -- @return true or false, errors -- function _instance:check_cxxsnippets(snippets, opt) opt = opt or {} opt.target = self opt.configs = self:_generate_build_configs(opt.configs, {sourcekind = "cxx"}) return sandbox_module.import("lib.detect.check_cxxsnippets", {anonymous = true})(snippets, opt) end -- check the given objc snippets? -- -- @param snippets the snippets -- @param opt the argument options, e.g. {includes = "xxx.h", configs = {defines = ""}} -- -- @return true or false, errors -- function _instance:check_msnippets(snippets, opt) opt = opt or {} opt.target = self opt.configs = self:_generate_build_configs(opt.configs, {sourcekind = "mm"}) return sandbox_module.import("lib.detect.check_msnippets", {anonymous = true})(snippets, opt) end -- check the given objc++ snippets? -- -- @param snippets the snippets -- @param opt the argument options, e.g. {includes = "xxx.h", configs = {defines = ""}} -- -- @return true or false, errors -- function _instance:check_mxxsnippets(snippets, opt) opt = opt or {} opt.target = self opt.configs = self:_generate_build_configs(opt.configs, {sourcekind = "mxx"}) return sandbox_module.import("lib.detect.check_mxxsnippets", {anonymous = true})(snippets, opt) end -- check the given fortran snippets? -- -- @param snippets the snippets -- @param opt the argument options, e.g. {configs = {defines = ""}, linkerkind = "fc", "cxx" ...} -- -- @return true or false, errors -- function _instance:check_fcsnippets(snippets, opt) opt = opt or {} opt.target = self opt.configs = self:_generate_build_configs(opt.configs, {sourcekind = "fc"}) return sandbox_module.import("lib.detect.check_fcsnippets", {anonymous = true})(snippets, opt) end -- check the given importfiles? -- -- @param names the import filenames (without .pc/.cmake extension), e.g. pkgconfig::libxml-2.0, cmake::CURL -- @param opt the argument options -- -- @return true or false, errors -- function _instance:check_importfiles(names, opt) opt = opt or {} if opt.PKG_CONFIG_PATH == nil then local PKG_CONFIG_PATH = {} local linkdirs = table.wrap(self:get("linkdirs") or "lib") local installdir = self:installdir() for _, linkdir in ipairs(linkdirs) do table.insert(PKG_CONFIG_PATH, path.join(installdir, linkdir, "pkgconfig")) end opt.PKG_CONFIG_PATH = PKG_CONFIG_PATH end if opt.CMAKE_PREFIX_PATH == nil then opt.CMAKE_PREFIX_PATH = self:installdir() end return sandbox_module.import("lib.detect.check_importfiles", {anonymous = true})(names or ("pkgconfig::" .. self:name()), opt) end -- the current mode is belong to the given modes? function package._api_is_mode(interp, ...) return config.is_mode(...) end -- the current platform is belong to the given platforms? function package._api_is_plat(interp, ...) local plat = package.targetplat() for _, v in ipairs(table.join(...)) do if v and plat == v then return true end end end -- the current platform is belong to the given architectures? function package._api_is_arch(interp, ...) local arch = package.targetarch() for _, v in ipairs(table.join(...)) do if v and arch:find("^" .. v:gsub("%-", "%%-") .. "$") then return true end end end -- the current host is belong to the given hosts? function package._api_is_host(interp, ...) return os.is_host(...) end -- the interpreter function package._interpreter() local interp = package._INTERPRETER if not interp then interp = interpreter.new() interp:api_define(package.apis()) interp:api_define(language.apis()) package._INTERPRETER = interp end return interp end -- get package memcache function package._memcache() return memcache.cache("core.base.package") end -- get project function package._project() local project = package._PROJECT if not project then if os.isfile(os.projectfile()) then project = require("project/project") end end return project end -- get global target platform of package function package.targetplat() local plat = package._memcache():get("target_plat") if plat == nil then if not plat and package._project() then local targetplat_root = package._project().get("target.plat") if targetplat_root then plat = targetplat_root end end if not plat then plat = config.get("plat") or os.subhost() end package._memcache():set("target_plat", plat) end return plat end -- get global target architecture of package function package.targetarch() local arch = package._memcache():get("target_arch") if arch == nil then if not arch and package._project() then local targetarch_root = package._project().get("target.arch") if targetarch_root then arch = targetarch_root end end if not arch then arch = config.get("arch") or os.subarch() end package._memcache():set("target_arch", arch) end return arch end -- get package apis function package.apis() return { values = { -- package.set_xxx "package.set_urls" , "package.set_kind" , "package.set_plat" -- deprecated , "package.set_arch" -- deprecated , "package.set_base" , "package.set_license" , "package.set_installtips" , "package.set_homepage" , "package.set_description" , "package.set_parallelize" , "package.set_sourcedir" , "package.set_cachedir" , "package.set_installdir" , "package.add_bindirs" , "package.add_schemes" -- package.add_xxx , "package.add_deps" , "package.add_urls" , "package.add_imports" , "package.add_configs" , "package.add_extsources" , "package.add_components" } , script = { -- package.on_xxx "package.on_source" , "package.on_load" , "package.on_fetch" , "package.on_check" , "package.on_download" , "package.on_install" , "package.on_test" , "package.on_component" } , keyvalues = { -- package.set_xxx "package.set_policy" -- package.add_xxx , "package.add_patches" , "package.add_resources" } , paths = { -- package.add_xxx "package.add_versionfiles" } , dictionary = { -- package.add_xxx "package.add_versions" } , custom = { -- is_xxx { "is_host", package._api_is_host } , { "is_mode", package._api_is_mode } , { "is_plat", package._api_is_plat } , { "is_arch", package._api_is_arch } } } end -- the cache directory function package.cachedir(opt) opt = opt or {} local cachedir = package._CACHEDIR if not cachedir then cachedir = os.getenv("XMAKE_PKG_CACHEDIR") or global.get("pkg_cachedir") or path.join(global.cachedir(), "packages") package._CACHEDIR = cachedir end if opt.rootonly then return cachedir end return path.join(cachedir, os.date("%y%m")) end -- the install directory function package.installdir() local installdir = package._INSTALLDIR if not installdir then installdir = os.getenv("XMAKE_PKG_INSTALLDIR") or global.get("pkg_installdir") or path.join(global.directory(), "packages") package._INSTALLDIR = installdir end return installdir end -- the search directories function package.searchdirs() local searchdirs = global.get("pkg_searchdirs") if searchdirs then return path.splitenv(searchdirs) end end -- load the package from the system directories function package.load_from_system(packagename) -- get it directly from cache first local instance = package._memcache():get2("packages", packagename) if instance then return instance end -- get package info local packageinfo = {} local is_thirdparty = false if packagename:find("::", 1, true) then -- get interpreter local interp = package._interpreter() -- on install script local on_install = function (pkg) local opt = {} local configs = table.clone(pkg:configs()) or {} opt.configs = configs opt.mode = pkg:is_debug() and "debug" or "release" opt.plat = pkg:plat() opt.arch = pkg:arch() opt.require_version = pkg:version_str() opt.buildhash = pkg:buildhash() opt.cachedir = pkg:cachedir() opt.installdir = pkg:installdir() if configs.runtimes then configs.runtimes = pkg:runtimes() end import("package.manager.install_package")(pkg:name(), opt) end -- make sandbox instance with the given script instance, errors = sandbox.new(on_install, {filter = interp:filter(), namespace = interp:namespace()}) if not instance then return nil, errors end -- save the install script packageinfo.install = instance:script() -- is third-party package? if not packagename:startswith("xmake::") then is_thirdparty = true end end -- new an instance instance = _instance.new(packagename, scopeinfo.new("package", packageinfo)) -- mark as system or 3rd package instance._is_system = true instance._is_thirdparty = is_thirdparty if is_thirdparty then -- add configurations for the 3rd package local configurations = sandbox_module.import("package.manager." .. packagename:split("::")[1]:lower() .. ".configurations", {try = true, anonymous = true}) if configurations then for name, conf in pairs(configurations()) do instance:add("configs", name, conf) end end -- disable parallelize for installation instance:set("parallelize", false) end -- save instance to the cache package._memcache():set2("packages", instance) return instance end -- load the package from the project file function package.load_from_project(packagename, project) -- get it directly from cache first local instance = package._memcache():get2("packages", packagename) if instance then return instance end -- load packages (with cache) local packages, errors = project.packages() if not packages then return nil, errors end -- get package info local packageinfo = packages[packagename] if packageinfo == nil and project.namespaces() then for _, namespace in ipairs(project.namespaces()) do packageinfo = packages[namespace .. "::" .. packagename] if packageinfo then packagename = namespace .. "::" .. packagename break end end end if packageinfo == nil then return end -- new an instance instance = _instance.new(packagename, packageinfo, {scriptdir = os.projectdir()}) package._memcache():set2("packages", instance) return instance end -- load the package from the package directory or package description file function package.load_from_repository(packagename, packagedir, opt) -- get it directly from cache first opt = opt or {} local instance = package._memcache():get2("packages", packagename) if instance then return instance end -- find the package script path local scriptpath = opt.packagefile if not opt.packagefile and packagedir then scriptpath = path.join(packagedir, "xmake.lua") end if not scriptpath or not os.isfile(scriptpath) then return nil, string.format("package %s not found!", packagename) end -- get interpreter local interp = package._interpreter() -- we need to modify plat/arch in description scope at same time -- if plat/arch are passed to add_requires. -- -- @see https://github.com/orgs/xmake-io/discussions/3439 -- -- e.g. add_requires("zlib~mingw", {plat = "mingw", arch = "x86_64"}) -- if opt.plat then package._memcache():set("target_plat", opt.plat) end if opt.arch then package._memcache():set("target_arch", opt.arch) end -- load script local ok, errors = interp:load(scriptpath) if not ok then return nil, errors end -- load package and disable filter, we will process filter after a while local results, errors = interp:make("package", true, false) if not results then return nil, errors end -- get package info local packageinfo = results[packagename] if not packageinfo then return nil, string.format("%s: package(%s) not found!", scriptpath, packagename) end -- new an instance instance = _instance.new(packagename, packageinfo, {scriptdir = path.directory(scriptpath), repo = opt.repo}) -- reset plat/arch if opt.plat then package._memcache():set("target_plat", nil) end if opt.arch then package._memcache():set("target_arch", nil) end -- save instance to the cache package._memcache():set2("packages", instance) return instance end -- new a package instance function package.new(...) return _instance.new(...) end -- return module return package