--!A cross-platform build utility based on Lua -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. -- You may obtain a copy of the License at -- -- http://www.apache.org/licenses/LICENSE-2.0 -- -- Unless required by applicable law or agreed to in writing, software -- distributed under the License is distributed on an "AS IS" BASIS, -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -- See the License for the specific language governing permissions and -- limitations under the License. -- -- Copyright (C) 2015-present, TBOOX Open Source Group. -- -- @author ruki -- @file target.lua -- -- define module local target = target or {} local _instance = _instance or {} -- load modules local bit = require("bit") local os = require("base/os") local path = require("base/path") local utils = require("base/utils") local table = require("base/table") local baseoption = require("base/option") local deprecated = require("base/deprecated") local memcache = require("cache/memcache") local rule = require("project/rule") local option = require("project/option") local config = require("project/config") local policy = require("project/policy") local project_package = require("project/package") local tool = require("tool/tool") local linker = require("tool/linker") local compiler = require("tool/compiler") local toolchain = require("tool/toolchain") local platform = require("platform/platform") local environment = require("platform/environment") local language = require("language/language") local sandbox = require("sandbox/sandbox") local sandbox_module = require("sandbox/modules/import/core/sandbox/module") -- new a target instance function _instance.new(name, info, project) local instance = table.inherit(_instance) instance._NAME = name instance._INFO = info instance._PROJECT = project instance._CACHEID = 1 return instance end -- get memcache function _instance:_memcache() local cache = self._MEMCACHE if not cache then cache = memcache.cache("core.project.target." .. tostring(self)) self._MEMCACHE = cache end return cache end -- load rule, move cache to target function _instance:_load_rule(ruleinst, suffix) -- init cache local key = ruleinst:name() .. (suffix and ("_" .. suffix) or "") local cache = self._RULES_LOADED or {} -- do load if cache[key] == nil then local on_load = ruleinst:script("load" .. (suffix and ("_" .. suffix) or "")) if on_load then local ok, errors = sandbox.load(on_load, self) cache[key] = {ok, errors} else cache[key] = {true} end end -- save cache self._RULES_LOADED = cache -- return results local results = cache[key] if results then return results[1], results[2] end end -- load rules function _instance:_load_rules(suffix) for _, r in ipairs(self:orderules()) do local ok, errors = self:_load_rule(r, suffix) if not ok then return false, errors end end return true end -- do before_load target and rules function _instance:_load_before() -- do before_load with target rules local ok, errors = self:_load_rules("before") if not ok then return false, errors end return true end -- do load target and rules function _instance:_load() -- do load with target rules local ok, errors = self:_load_rules() if not ok then return false, errors end -- do load for target local on_load = self:script("load") if on_load then ok, errors = sandbox.load(on_load, self) if not ok then return false, errors end end return true end -- do after_load target and rules function _instance:_load_after() -- enter the environments of the target packages local oldenvs = os.addenvs(self:pkgenvs()) -- do after_load with target rules local ok, errors = self:_load_rules("after") if not ok then return false, errors end -- leave the environments of the target packages os.setenvs(oldenvs) return true end -- get the copied files function _instance:_copiedfiles(filetype, outputdir, pathfilter) -- no copied files? local copiedfiles = self:get(filetype) if not copiedfiles then return end -- get the extra information local extrainfo = table.wrap(self:get("__extra_" .. filetype)) -- get the source paths and destinate paths local srcfiles = {} local dstfiles = {} local fileinfos = {} for _, copiedfile in ipairs(table.wrap(copiedfiles)) do -- get the root directory local rootdir, count = copiedfile:gsub("|.*$", ""):gsub("%(.*%)$", "") if count == 0 then rootdir = nil end -- remove '(' and ')' local srcpaths = copiedfile:gsub("[%(%)]", "") if srcpaths then -- get the source paths srcpaths = os.match(srcpaths) if srcpaths and #srcpaths > 0 then -- add the source copied files table.join2(srcfiles, srcpaths) -- the copied directory exists? if outputdir then -- get the file info local fileinfo = extrainfo[copiedfile] or {} -- get the prefix directory local prefixdir = fileinfo.prefixdir -- add the destinate copied files for _, srcpath in ipairs(srcpaths) do -- get the destinate directory local dstdir = outputdir if prefixdir then dstdir = path.join(dstdir, prefixdir) end -- the destinate file local dstfile = nil if rootdir then dstfile = path.absolute(path.relative(srcpath, rootdir), dstdir) else dstfile = path.join(dstdir, path.filename(srcpath)) end assert(dstfile) -- modify filename if fileinfo.filename then dstfile = path.join(path.directory(dstfile), fileinfo.filename) end -- filter the destinate file path if pathfilter then dstfile = pathfilter(dstfile, fileinfo) end -- add it table.insert(dstfiles, dstfile) table.insert(fileinfos, fileinfo) end end end end end return srcfiles, dstfiles, fileinfos end -- get the visibility, private: 1, interface: 2, public: 3 = 1 | 2 function _instance:_visibility(opt) local visibility = 1 if opt then if opt.interface then visibility = 2 elseif opt.public then visibility = 3 end end return visibility end -- invalidate the previous cache function _instance:_invalidate(name) self._CACHEID = self._CACHEID + 1 self._POLICIES = nil -- we need flush the source files cache if target/files are modified, e.g. `target:add("files", "xxx.c")` if name == "files" then self._SOURCEFILES = nil end end -- get the target info -- -- e.g. -- -- default: get private -- - target:get("cflags") -- - target:get("cflags", {private = true}) -- -- get private and interface -- - target:get("cflags", {public = true}) -- -- get interface -- - target:get("cflags", {interface = true}) -- -- get raw reference of values -- - target:get("cflags", {rawref = true}) -- function _instance:get(name, opt) -- get values local values = self._INFO:get(name) -- get thr required visibility local vs_private = 1 local vs_interface = 2 local vs_public = 3 -- all local vs_required = self:_visibility(opt) -- get all values? (private and interface) if vs_required == vs_public or (opt and opt.rawref) then return values end -- get the extra configuration local extraconf = self:extraconf(name) if extraconf then -- filter values for public, private or interface if be not dictionary if not table.is_dictionary(values) then local results = {} for _, value in ipairs(table.wrap(values)) do local vs_conf = self:_visibility(extraconf[value]) if bit.band(vs_required, vs_conf) ~= 0 then table.insert(results, value) end end if #results > 0 then return table.unwrap(results) end else return values end else -- only get thr private values if bit.band(vs_required, vs_private) ~= 0 then return values end end end -- get values from target dependencies function _instance:get_from_deps(name, opt) local values = {} local orderdeps = self:orderdeps() local total = #orderdeps for idx, _ in ipairs(orderdeps) do local dep = orderdeps[total + 1 - idx] local depinherit = self:extraconf("deps", dep:name(), "inherit") if depinherit == nil or depinherit then table.join2(values, dep:get(name, opt)) table.join2(values, dep:get_from_opts(name, opt)) table.join2(values, dep:get_from_pkgs(name, opt)) end end return values end -- get values from target options with {interface|public = ...} function _instance:get_from_opts(name, opt) local values = {} for _, opt_ in ipairs(self:orderopts(opt)) do table.join2(values, table.wrap(opt_:get(name))) end return values end -- get values from target packages with {interface|public = ...} function _instance:get_from_pkgs(name, opt) local values = {} for _, pkg in ipairs(self:orderpkgs(opt)) do -- uses them instead of the builtin configs if exists extra package config -- e.g. `add_packages("xxx", {links = "xxx"})` local configinfo = self:pkgconfig(pkg:name()) if configinfo and configinfo[name] then table.join2(values, configinfo[name]) else -- uses the builtin package configs table.join2(values, pkg:get(name)) end end return values end -- set the value to the target info function _instance:set(name, ...) self._INFO:apival_set(name, ...) self:_invalidate(name) end -- add the value to the target info function _instance:add(name, ...) self._INFO:apival_add(name, ...) self:_invalidate(name) end -- remove the value to the target info function _instance:del(name, ...) self._INFO:apival_del(name, ...) self:_invalidate(name) end -- get the extra configuration function _instance:extraconf(name, item, key) return self._INFO:extraconf(name, item, key) end -- get user private data function _instance:data(name) return self._DATA and self._DATA[name] 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 values function _instance:values(name, sourcefile) -- get values from the source file first local values = {} if sourcefile then local fileconfig = self:fileconfig(sourcefile) if fileconfig then local filevalues = fileconfig.values if filevalues then -- we use '_' to simplify setting, for example: -- -- add_files("xxx.mof", {values = {wdk_mof_header = "xxx.h"}}) -- add_files("xxx.mof", {values = {["wdk.mof.header"] = "xxx.h"}}) -- table.join2(values, filevalues[name] or filevalues[name:gsub("%.", "_")]) end end end -- get values from target table.join2(values, self:get("values." .. name)) if #values > 0 then values = table.unwrap(values) else values = nil end return values end -- set values function _instance:values_set(name, ...) self:set("values." .. name, ...) end -- add values function _instance:values_add(name, ...) self:add("values." .. name, ...) end -- get the target info function _instance:info() return self._INFO:info() end -- get the type: target function _instance:type() return "target" end -- get the target name function _instance:name() return self._NAME end -- get the target kind function _instance:kind() return self:get("kind") or "binary" end -- get the target kind (deprecated) function _instance:targetkind() return self:kind() end -- get the platform of this target function _instance:plat() return self:get("plat") or config.get("plat") or os.host() end -- get the architecture of this target function _instance:arch() return self:get("arch") or config.get("arch") or os.arch() end -- the current target is belong to the given platforms? function _instance:is_plat(...) local plat = self:plat() for _, v in ipairs(table.join(...)) do if v and plat == v then return true end end end -- the current target is belong to the given architectures? function _instance:is_arch(...) local arch = self:arch() for _, v in ipairs(table.join(...)) do if v and arch:find("^" .. v:gsub("%-", "%%-") .. "$") then return true end end end -- get the platform instance function _instance:platform() local platform_inst = self._PLATFORM if platform_inst == nil then platform_inst, errors = platform.load(self:plat(), self:arch()) if not platform_inst then os.raise(errors) end self._PLATFORM = platform_inst end return platform_inst end -- get the cache key function _instance:cachekey() return string.format("%s_%d", tostring(self), self._CACHEID) end -- get the target version function _instance:version() -- get version and build version local version = self:get("version") local version_build = nil if version then local version_extra = self:get("__extra_version") if version_extra then version_build = self._VERSION_BUILD if not version_build then version_build = table.wrap(version_extra[version]).build if type(version_build) == "string" then version_build = os.date(version_build, os.time()) self._VERSION_BUILD = version_build end end end end return version, version_build end -- get the target license function _instance:license() return self:get("license") end -- get the target 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 base name of target file function _instance:basename() local filename = self:get("filename") if filename then return path.basename(filename) end return self:get("basename") or self:name() end -- get the target linker function _instance:linker() -- get it from cache first if self._LINKER then return self._LINKER end -- get the linker instance local instance, errors = linker.load(self:kind(), self:sourcekinds(), self) if not instance then os.raise(errors) end self._LINKER = instance return instance end -- make linking command for this target function _instance:linkcmd(objectfiles) return self:linker():linkcmd(objectfiles or self:objectfiles(), self:targetfile(), {target = self}) end -- make linking arguments for this target function _instance:linkargv(objectfiles) return self:linker():linkargv(objectfiles or self:objectfiles(), self:targetfile(), {target = self}) end -- make link flags for the given target function _instance:linkflags() return self:linker():linkflags({target = self}) end -- get the given dependent target function _instance:dep(name) local deps = self:deps() if deps then return deps[name] end end -- get target deps function _instance:deps() return self._DEPS end -- get target ordered deps function _instance:orderdeps() return self._ORDERDEPS end -- get target rules function _instance:rules() return self._RULES end -- get target ordered rules function _instance:orderules() return self._ORDERULES end -- get target rule from the given rule name function _instance:rule(name) if self._RULES then return self._RULES[name] end end -- is phony target? function _instance:is_phony() local targetkind = self:kind() return not targetkind or targetkind == "phony" end -- is binary target? function _instance:is_binary() return self:kind() == "binary" end -- is shared library target? function _instance:is_shared() return self:kind() == "shared" end -- is static library target? function _instance:is_static() return self:kind() == "static" end -- is library target? function _instance:is_library() return self:is_static() or self:is_shared() end -- is default target? function _instance:is_default() local default = self:get("default") return default == nil or default == true end -- is enabled? function _instance:is_enabled() return self:get("enabled") ~= false end -- get the enabled option function _instance:opt(name) return self:opts()[name] end -- get the enabled options function _instance:opts() -- attempt to get it from cache first if self._OPTS_ENABLED then return self._OPTS_ENABLED end -- load options if be enabled self._OPTS_ENABLED = {} for _, opt in ipairs(self:orderopts()) do self._OPTS_ENABLED[opt:name()] = opt end -- get it return self._OPTS_ENABLED end -- get the enabled ordered options with {public|interface = ...} function _instance:orderopts(opt) opt = opt or {} local cachekey = "orderopts" if opt.public then cachekey = cachekey .. "_public" elseif opt.interface then cachekey = cachekey .. "_interface" end local orderopts = self:_memcache():get(cachekey) if not orderopts then -- load options if be enabled orderopts = {} for _, name in ipairs(table.wrap(self:get("options"))) do local opt_ = nil if config.get(name) then opt_ = option.load(name) end if opt_ then table.insert(orderopts, opt_) end end -- load options from packages if no require info, be compatible with the option package in (*.pkg) for _, name in ipairs(table.wrap(self:get("packages"))) do if not project_package.load(name) then local opt_ = nil if config.get(name) then opt_ = option.load(name) end if opt_ then table.insert(orderopts, opt_) end end end self:_memcache():set(cachekey, orderopts) end return orderopts end -- get the enabled package function _instance:pkg(name) return self:pkgs()[name] end -- get the enabled packages function _instance:pkgs() -- attempt to get it from cache first if self._PKGS_ENABLED then return self._PKGS_ENABLED end -- load packages if be enabled self._PKGS_ENABLED = {} for _, pkg in ipairs(self:orderpkgs()) do self._PKGS_ENABLED[pkg:name()] = pkg end return self._PKGS_ENABLED end -- get the required packages with {interface|public = ..} function _instance:orderpkgs(opt) opt = opt or {} local cachekey = "orderpkgs" if opt.public then cachekey = cachekey .. "_public" elseif opt.interface then cachekey = cachekey .. "_interface" end local packages = self:_memcache():get(cachekey) if not packages then packages = {} local requires = self._PROJECT.required_packages() if requires then for _, packagename in ipairs(table.wrap(self:get("packages", opt))) do local pkg = requires[packagename] if pkg and pkg:enabled() then table.insert(packages, pkg) end end end self:_memcache():set(cachekey, packages) end return packages end -- get the environments of packages function _instance:pkgenvs() local pkgenvs = self._PKGENVS if pkgenvs == nil then for _, pkgname in ipairs(table.wrap(self:get("packages"))) do local pkg = self:pkg(pkgname) if pkg then local envs = pkg:get("envs") if envs then for name, values in pairs(envs) do if type(values) == "table" then values = path.joinenv(values) end pkgenvs = pkgenvs or {} if pkgenvs[name] then pkgenvs[name] = pkgenvs[name] .. path.envsep() .. values else pkgenvs[name] = values end end end end end self._PKGENVS = pkgenvs or false end return pkgenvs or nil end -- get the config info of the given package function _instance:pkgconfig(pkgname) local extra_packages = self:get("__extra_packages") if extra_packages then return extra_packages[pkgname] end end -- get the object files directory function _instance:objectdir(opt) -- the object directory local objectdir = self:get("objectdir") if not objectdir then objectdir = path.join(config.buildir(), ".objs") end objectdir = path.join(objectdir, self:name()) -- get root directory of target if opt and opt.root then return objectdir end -- append plat sub-directory local plat = self:plat() if plat then objectdir = path.join(objectdir, plat) end -- append arch sub-directory local arch = self:arch() if arch then objectdir = path.join(objectdir, arch) end -- append mode sub-directory local mode = config.get("mode") if mode then objectdir = path.join(objectdir, mode) end return objectdir end -- get the dependent files directory function _instance:dependir(opt) -- init the dependent directory local dependir = self:get("dependir") if not dependir then dependir = path.join(config.buildir(), ".deps") end dependir = path.join(dependir, self:name()) -- get root directory of target if opt and opt.root then return dependir end -- append plat sub-directory local plat = self:plat() if plat then dependir = path.join(dependir, plat) end -- append arch sub-directory local arch = self:arch() if arch then dependir = path.join(dependir, arch) end -- append mode sub-directory local mode = config.get("mode") if mode then dependir = path.join(dependir, mode) end return dependir end -- get the autogen files directory function _instance:autogendir(opt) -- the autogen directory local autogendir = path.join(config.buildir(), ".gens", self:name()) -- get root directory of target if opt and opt.root then return autogendir end -- append plat sub-directory local plat = self:plat() if plat then autogendir = path.join(autogendir, plat) end -- append arch sub-directory local arch = self:arch() if arch then autogendir = path.join(autogendir, arch) end -- append mode sub-directory local mode = config.get("mode") if mode then autogendir = path.join(autogendir, mode) end return autogendir end -- get the autogen file path from the given source file path function _instance:autogenfile(sourcefile, opt) -- get relative directory in the autogen directory local relativedir = nil local origindir = path.directory(path.absolute(sourcefile)) local autogendir = path.absolute(self:autogendir()) if origindir:startswith(autogendir) then relativedir = path.join("gens", path.relative(origindir, autogendir)) end -- get relative directory in the source directory if not relativedir then relativedir = path.directory(sourcefile) end -- translate path -- -- e.g. -- -- src/xxx.c -- project/xmake.lua -- build/.objs -- build/.gens -- -- objectfile: project/build/.objs/xxxx/../../xxx.c will be out of range for objectdir -- autogenfile: project/build/.gens/xxxx/../../xxx.c will be out of range for autogendir -- -- we need replace '..' to '__' in this case -- if path.is_absolute(relativedir) and os.host() == "windows" then relativedir = relativedir:gsub(":[\\/]*", '\\') -- replace C:\xxx\ => C\xxx\ end relativedir = relativedir:gsub("%.%.", "__") local rootdir = (opt and opt.rootdir) and opt.rootdir or self:autogendir() if relativedir ~= "." then rootdir = path.join(rootdir, relativedir) end return path.join(rootdir, (opt and opt.filename) and opt.filename or path.filename(sourcefile)) end -- get the target directory function _instance:targetdir() -- the target directory local targetdir = self:get("targetdir") if not targetdir then -- get build directory targetdir = config.buildir() -- append plat sub-directory local plat = self:plat() if plat then targetdir = path.join(targetdir, plat) end -- append arch sub-directory local arch = self:arch() if arch then targetdir = path.join(targetdir, arch) end -- append mode sub-directory local mode = config.get("mode") if mode then targetdir = path.join(targetdir, mode) end end return targetdir end -- get the target file name function _instance:filename() -- only compile objects? no target file local targetkind = self:kind() if targetkind == "object" then return end -- make the target file name and attempt to use the format of linker first local filename = self:get("filename") if not filename then local prefixname = self:get("prefixname") local suffixname = self:get("suffixname") local extension = self:get("extension") filename = target.filename(self:basename(), targetkind, { plat = self:plat(), arch = self:arch(), prefixname = prefixname, suffixname = suffixname, extension = extension, format = self:linker():format(targetkind)}) end return filename end -- get the link name only for static/shared library function _instance:linkname() if self:is_static() or self:is_shared() then local filename = self:get("filename") if filename then return target.linkname(filename) else local linkname = self:basename() local suffixname = self:get("suffixname") if suffixname then linkname = linkname .. suffixname end return linkname end end end -- get the target file function _instance:targetfile() local filename = self:filename() if filename then return path.join(self:targetdir(), filename) end end -- get the symbol file function _instance:symbolfile() -- the target directory local targetdir = self:targetdir() assert(targetdir and type(targetdir) == "string") -- the symbol file name local filename = target.filename(self:basename(), "symbol", {plat = self:plat(), arch = self:arch()}) assert(filename) -- make the symbol file path return path.join(targetdir, filename) end -- get the script directory of xmake.lua function _instance:scriptdir() return self:get("__scriptdir") end -- TODO get header directory (deprecated) function _instance:headerdir() return self:get("headerdir") or config.buildir() end -- get configuration output directory function _instance:configdir() return self:get("configdir") or config.buildir() end -- get run directory function _instance:rundir() return baseoption.get("workdir") or self:get("rundir") or path.directory(self:targetfile()) end -- get install directory function _instance:installdir() -- get it from the cache local installdir = baseoption.get("installdir") if not installdir then -- DESTDIR: be compatible with https://www.gnu.org/prep/standards/html_node/DESTDIR.html installdir = self:get("installdir") or os.getenv("INSTALLDIR") or os.getenv("PREFIX") or os.getenv("DESTDIR") or platform.get("installdir") if installdir then installdir = installdir:trim() end end return installdir end -- get rules of the source file function _instance:filerules(sourcefile) -- add rules from file config local rules = {} local override = false local fileconfig = self:fileconfig(sourcefile) if fileconfig then local filerules = fileconfig.rules or fileconfig.rule if filerules then override = filerules.override for _, rulename in ipairs(table.wrap(filerules)) do local r = self._PROJECT.rule(rulename) or rule.rule(rulename) if r then table.insert(rules, r) end end end end -- override? e.g. add_files("src/*.c", {rules = {"xxx", override = true}}) if override then return rules, true end -- load all rules for this target with sourcekinds and extensions local key2rules = self._KEY2RULES if not key2rules then key2rules = {} for _, r in pairs(table.wrap(self:rules())) do -- we can also get sourcekinds from add_rules("xxx", {sourcekinds = "cxx"}) local rule_sourcekinds = self:extraconf("rules", r:name(), "sourcekinds") or r:get("sourcekinds") for _, sourcekind in ipairs(table.wrap(rule_sourcekinds)) do key2rules[sourcekind] = key2rules[sourcekind] or {} table.insert(key2rules[sourcekind], r) end -- we can also get extensions from add_rules("xxx", {extensions = ".cpp"}) local rule_extensions = self:extraconf("rules", r:name(), "extensions") or r:get("extensions") for _, extension in ipairs(table.wrap(rule_extensions)) do extension = extension:lower() key2rules[extension] = key2rules[extension] or {} table.insert(key2rules[extension], r) end end self._KEY2RULES = key2rules end -- get target rules from the given sourcekind or extension local rules_override = {} local filename = path.filename(sourcefile):lower() for _, r in ipairs(table.wrap(key2rules[path.extension(filename, 2)] or key2rules[path.extension(filename)])) do if self:extraconf("rules", r:name(), "override") then table.insert(rules_override, r) else table.insert(rules, r) end end for _, r in ipairs(table.wrap(key2rules[self:sourcekind_of(filename)])) do if self:extraconf("rules", r:name(), "override") then table.insert(rules_override, r) else table.insert(rules, r) end end -- we will use overrided rules first, e.g. add_rules("xxx", {override = true}) return #rules_override > 0 and rules_override or rules end -- get the config info of the given source file function _instance:fileconfig(sourcefile) -- get files config local filesconfig = self._FILESCONFIG if not filesconfig then filesconfig = {} for filepath, fileconfig in pairs(table.wrap(self:get("__extra_files"))) do -- match source files local results = os.match(filepath) if #results == 0 then local sourceinfo = (self:get("__sourceinfo_files") or {})[filepath] or {} utils.warning("cannot match %s(%s).add_files(\"%s\") at %s:%d", self:type(), self:name(), filepath, sourceinfo.file or "", sourceinfo.line or -1) end -- process source files for _, file in ipairs(results) do -- convert to the relative path if path.is_absolute(file) then file = path.relative(file, os.projectdir()) end -- save it filesconfig[file] = fileconfig end end self._FILESCONFIG = filesconfig end -- get file config return filesconfig[sourcefile] end -- set the config info to the given source file function _instance:fileconfig_set(sourcefile, info) -- get files config local filesconfig = self._FILESCONFIG or {} -- set config info filesconfig[sourcefile] = info -- update files config self._FILESCONFIG = filesconfig end -- get the source files function _instance:sourcefiles() -- cached? return it directly if self._SOURCEFILES then return self._SOURCEFILES, false end -- get files local files = self:get("files") if not files then return {}, false end -- match files local i = 1 local count = 0 local sourcefiles = {} local sourcefiles_deleted = {} local sourcefiles_inserted = {} local deleted_count = 0 local targetcache = memcache.cache("core.project.target") for _, file in ipairs(table.wrap(files)) do -- mark as deleted files? local deleted = false if file:startswith("__del_") then file = file:sub(7) deleted = true end -- find source files and try to cache the matching results of os.match across targets -- @see https://github.com/xmake-io/xmake/issues/1353 local results = targetcache:get2("sourcefiles", file) if not results then if deleted then results = {file} else results = os.files(file) if #results == 0 then -- attempt to find source directories if maybe compile it as directory with the custom rules if #self:filerules(file) > 0 then results = os.dirs(file) end end -- Even if the current source file does not exist yet, we always add it. -- This is usually used for some rules that automatically generate code files, -- because they ensure that the code files have been generated before compilation. -- -- @see https://github.com/xmake-io/xmake/issues/1540 -- -- e.g. add_files("src/test.c", {always_added = true}) -- if #results == 0 and self:extraconf("files", file, "always_added") then results = {file} end end targetcache:set2("sourcefiles", file, results) end if #results == 0 then local sourceinfo = (self:get("__sourceinfo_files") or {})[file] or {} utils.warning("cannot match %s(%s).%s_files(\"%s\") at %s:%d", self:type(), self:name(), (deleted and "del" or "add"), file, sourceinfo.file or "", sourceinfo.line or -1) end -- process source files for _, sourcefile in ipairs(results) do -- convert to the relative path if path.is_absolute(sourcefile) then sourcefile = path.relative(sourcefile, os.projectdir()) end -- add or delete it if deleted then deleted_count = deleted_count + 1 table.insert(sourcefiles_deleted, sourcefile) elseif not sourcefiles_inserted[sourcefile] then table.insert(sourcefiles, sourcefile) sourcefiles_inserted[sourcefile] = true end end end -- remove all deleted source files if deleted_count > 0 then for i = #sourcefiles, 1, -1 do local sourcefile = sourcefiles[i] for _, deletefile in ipairs(sourcefiles_deleted) do local pattern = path.translate(deletefile:gsub("|.*$", "")) if pattern:sub(1, 2):find('%.[/\\]') then pattern = pattern:sub(3) end pattern = path.pattern(pattern) if sourcefile:match(pattern) then table.remove(sourcefiles, i) end end end end self._SOURCEFILES = sourcefiles -- ok and sourcefiles are modified return sourcefiles, true end -- get object file from source file function _instance:objectfile(sourcefile) return self:autogenfile(sourcefile, {rootdir = self:objectdir(), filename = target.filename(path.filename(sourcefile), "object", {plat = self:plat(), arch = self:arch()})}) end -- get the object files function _instance:objectfiles() -- get source batches local sourcebatches, modified = self:sourcebatches() -- cached? return it directly if self._OBJECTFILES and not modified then return self._OBJECTFILES end -- get object files from source batches local objectfiles = {} local batchcount = 0 local sourcebatches = self:sourcebatches() local orderkeys = table.keys(sourcebatches) table.sort(orderkeys) -- @note we need guarantee the order of objectfiles for depend.is_changed() and etc. for _, k in ipairs(orderkeys) do local sourcebatch = sourcebatches[k] table.join2(objectfiles, sourcebatch.objectfiles) batchcount = batchcount + 1 end -- some object files may be repeat and appear link errors if multi-batches exists, so we need remove all repeat object files -- e.g. add_files("src/*.c", {rules = {"rule1", "rule2"}}) local remove_repeat = batchcount > 1 -- get object files from all dependent targets (object kind) if self:orderdeps() then for _, dep in ipairs(self:orderdeps()) do if dep:kind() == "object" then table.join2(objectfiles, dep:objectfiles()) remove_repeat = true end end end -- remove repeat object files if remove_repeat then objectfiles = table.unique(objectfiles) end -- cache it self._OBJECTFILES = objectfiles return objectfiles end -- TODO get the header files, get("headers") (deprecated) function _instance:headers(outputdir) return self:headerfiles(outputdir, true) end -- get the header files -- -- default: get("headers") + get("headerfiles") -- only_deprecated: get("headers") -- function _instance:headerfiles(outputdir, only_deprecated) -- get header files? local headers = self:get("headers") -- TODO deprecated if not only_deprecated then headers = table.join(headers or {}, self:get("headerfiles")) end if not headers then return end -- get the installed header directory local headerdir = outputdir if not headerdir then if only_deprecated then headerdir = self:headerdir() elseif self:installdir() then headerdir = path.join(self:installdir(), "include") end end -- get the extra information local extrainfo = table.wrap(self:get("__extra_headerfiles")) -- get the source paths and destinate paths local srcheaders = {} local dstheaders = {} for _, header in ipairs(table.wrap(headers)) do -- get the root directory local rootdir, count = header:gsub("|.*$", ""):gsub("%(.*%)$", "") if count == 0 then rootdir = nil end -- remove '(' and ')' local srcpaths = header:gsub("[%(%)]", "") if srcpaths then -- get the source paths srcpaths = os.match(srcpaths) if srcpaths then -- add the source headers table.join2(srcheaders, srcpaths) -- get the destinate directories if the install directory exists if headerdir then -- get the prefix directory local prefixdir = (extrainfo[header] or {}).prefixdir -- add the destinate headers for _, srcpath in ipairs(srcpaths) do -- get the destinate directory local dstdir = headerdir if prefixdir then dstdir = path.join(dstdir, prefixdir) end -- the destinate header local dstheader = nil if rootdir then dstheader = path.absolute(path.relative(srcpath, rootdir), dstdir) else dstheader = path.join(dstdir, path.filename(srcpath)) end assert(dstheader) -- add it table.insert(dstheaders, dstheader) end end end end end -- ok? return srcheaders, dstheaders end -- get the configuration files function _instance:configfiles(outputdir) return self:_copiedfiles("configfiles", outputdir or self:configdir(), function (dstpath, fileinfo) if dstpath:endswith(".in") then dstpath = dstpath:sub(1, -4) end return dstpath end) end -- get the install files function _instance:installfiles(outputdir) return self:_copiedfiles("installfiles", outputdir or self:installdir()) end -- get depend file from object file function _instance:dependfile(objectfile) -- get the dependent original file and directory, @note relative to the root directory local originfile = path.absolute(objectfile and objectfile or self:targetfile()) local origindir = path.directory(originfile) -- get relative directory in the object directory local relativedir = nil local objectdir = path.absolute(self:objectdir()) if origindir:startswith(objectdir) then relativedir = path.relative(origindir, objectdir) end -- get relative directory in the target directory if not relativedir then local targetdir = path.absolute(self:targetdir()) if origindir:startswith(targetdir) then relativedir = path.relative(origindir, targetdir) end end -- get relative directory in the autogen directory if not relativedir then local autogendir = path.absolute(self:autogendir()) if origindir:startswith(autogendir) then relativedir = path.join("gens", path.relative(origindir, autogendir)) end end -- get relative directory in the build directory if not relativedir then local buildir = path.absolute(config.buildir()) if origindir:startswith(buildir) then relativedir = path.join("build", path.relative(origindir, buildir)) end end -- get relative directory in the project directory if not relativedir then local projectdir = os.projectdir() if origindir:startswith(projectdir) then relativedir = path.relative(origindir, projectdir) end end -- get the relative directory from the origin file if not relativedir then relativedir = origindir end if path.is_absolute(relativedir) and os.host() == "windows" then relativedir = relativedir:gsub(":[\\/]*", '\\') -- replace C:\xxx\ => C\xxx\ end -- originfile: project/build/.objs/xxxx/../../xxx.c will be out of range for objectdir -- -- we need replace '..' to '__' in this case -- relativedir = relativedir:gsub("%.%.", "__") -- make dependent file -- full file name(not base) to avoid name-clash of original file return path.join(self:dependir(), relativedir, path.filename(originfile) .. ".d") end -- get the dependent include files function _instance:dependfiles() -- get source batches local sourcebatches, modified = self:sourcebatches() -- cached? return it directly if self._DEPENDFILES and not modified then return self._DEPENDFILES end -- get dependent files from source batches local dependfiles = {} for _, sourcebatch in pairs(self:sourcebatches()) do table.join2(dependfiles, sourcebatch.dependfiles) end -- cache it self._DEPENDFILES = dependfiles -- ok? return dependfiles end -- get the sourcekind for the given source file function _instance:sourcekind_of(sourcefile) -- get the sourcekind of this source file local sourcekind = language.sourcekind_of(sourcefile) local fileconfig = self:fileconfig(sourcefile) if fileconfig and fileconfig.sourcekind then -- we can override the sourcekind, e.g. add_files("*.c", {sourcekind = "cxx"}) sourcekind = fileconfig.sourcekind end return sourcekind end -- get the kinds of sourcefiles -- -- e.g. cc cxx mm mxx as ... -- function _instance:sourcekinds() local sourcekinds = self._SOURCEKINDS if not sourcekinds then sourcekinds = {} for _, sourcefile in pairs(self:sourcefiles()) do local sourcekind = self:sourcekind_of(sourcefile) if sourcekind then table.insert(sourcekinds, sourcekind) end end sourcekinds = table.unique(sourcekinds) self._SOURCEKINDS = sourcekinds end return sourcekinds end -- get source count function _instance:sourcecount() return #self:sourcefiles() end -- get source batches function _instance:sourcebatches() -- get source files local sourcefiles, modified = self:sourcefiles() -- cached? return it directly if self._SOURCEBATCHES and not modified then return self._SOURCEBATCHES, false end -- make source batches for each source kinds local sourcebatches = {} for _, sourcefile in ipairs(sourcefiles) do -- get file rules local filerules, override = self:filerules(sourcefile) if #filerules == 0 then os.raise("unknown source file: %s", sourcefile) end -- add source batch for the file rules for _, filerule in ipairs(filerules) do -- get rule name local rulename = filerule:name() -- make this batch local sourcebatch = sourcebatches[rulename] or {sourcefiles = {}} sourcebatches[rulename] = sourcebatch -- save the rule name sourcebatch.rulename = rulename -- add source file to this batch table.insert(sourcebatch.sourcefiles, sourcefile) -- attempt to get source kind from the builtin languages local sourcekind = self:sourcekind_of(sourcefile) if sourcekind and filerule:get("sourcekinds") and not override then -- save source kind sourcebatch.sourcekind = sourcekind -- insert object files to source batches sourcebatch.objectfiles = sourcebatch.objectfiles or {} sourcebatch.dependfiles = sourcebatch.dependfiles or {} local objectfile = self:objectfile(sourcefile, sourcekind) table.insert(sourcebatch.objectfiles, objectfile) table.insert(sourcebatch.dependfiles, self:dependfile(objectfile)) end end end -- cache it self._SOURCEBATCHES = sourcebatches -- ok? return sourcebatches, modified end -- get xxx_script function _instance:script(name, generic) -- get script local script = self:get(name) local result = nil if type(script) == "function" then result = script elseif type(script) == "table" then -- get plat and arch local plat = self:plat() local arch = self:arch() -- match pattern -- -- `@linux` -- `@linux|x86_64` -- `@macosx,linux` -- `android@macosx,linux` -- `android|armeabi-v7a@macosx,linux` -- `android|armeabi-v7a@macosx,linux|x86_64` -- `android|armeabi-v7a@linux|x86_64` -- for _pattern, _script in pairs(script) do local hosts = {} local hosts_spec = false _pattern = _pattern:gsub("@(.+)", function (v) for _, host in ipairs(v:split(',')) do hosts[host] = true hosts_spec = true end return "" end) if not _pattern:startswith("__") and (not hosts_spec or hosts[os.subhost() .. '|' .. os.subarch()] or hosts[os.subhost()]) and (_pattern:trim() == "" or (plat .. '|' .. arch):find('^' .. _pattern .. '$') or plat:find('^' .. _pattern .. '$')) then result = _script break end end -- get generic script result = result or script["__generic__"] or generic end -- only generic script result = result 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, {anonymous = true}) end end end return result end -- TODO get the config header version (deprecated) function _instance:configversion() -- get the config version and build version local version = nil local buildversion = nil local configheader = self:get("config_header") local configheader_extra = self:get("__extra_config_header") if type(configheader_extra) == "table" then version = table.wrap(configheader_extra[configheader]).version buildversion = self._CONFIGHEADER_BUILDVERSION if not buildversion then buildversion = table.wrap(configheader_extra[configheader]).buildversion if buildversion then buildversion = os.date(buildversion, os.time()) end self._CONFIGHEADER_BUILDVERSION = buildversion end end -- ok? return version, buildversion end -- get the config header prefix function _instance:configprefix() -- get the config prefix local configprefix = nil local configheader = self:get("config_header") local configheader_extra = self:get("__extra_config_header") if type(configheader_extra) == "table" then configprefix = table.wrap(configheader_extra[configheader]).prefix end return configprefix end -- get the config header files (deprecated) function _instance:configheader(outputdir) -- get config header local configheader = self:get("config_header") if not configheader then return end -- get the root directory local rootdir, count = configheader:gsub("|.*$", ""):gsub("%(.*%)$", "") if count == 0 then rootdir = nil end -- remove '(' and ')' configheader = configheader:gsub("[%(%)]", "") -- get the output header local outputheader = nil if outputdir then if rootdir then outputheader = path.absolute(path.relative(configheader, rootdir), outputdir) else outputheader = path.join(outputdir, path.filename(configheader)) end end -- ok return configheader, outputheader end -- get the precompiled header file (xxx.[h|hpp|inl]) -- -- @param langkind c/cxx -- function _instance:pcheaderfile(langkind) return self:get("p" .. langkind .. "header") end -- get the output of precompiled header file (xxx.h.pch) -- -- @param langkind c/cxx -- function _instance:pcoutputfile(langkind) -- init cache self._PCOUTPUTFILES = self._PCOUTPUTFILES or {} -- get it from the cache first local pcoutputfile = self._PCOUTPUTFILES[langkind] if pcoutputfile then return pcoutputfile end -- get the precompiled header file in the object directory local pcheaderfile = self:pcheaderfile(langkind) if pcheaderfile then -- is gcc? local is_gcc = false local _, toolname = self:tool(langkind == "c" and "cc" or "cxx") if toolname and (toolname == "gcc" or toolname == "gxx") then is_gcc = true end -- make precompiled output file -- -- @note gcc has not -include-pch option to set the pch file path -- pcoutputfile = self:objectfile(pcheaderfile) pcoutputfile = path.join(path.directory(pcoutputfile), path.basename(pcoutputfile) .. (is_gcc and ".gch" or ".pch")) -- save to cache self._PCOUTPUTFILES[langkind] = pcoutputfile return pcoutputfile end end -- get the toolchains function _instance:toolchains() local toolchains = self:_memcache():get("toolchains") if toolchains == nil then -- load target toolchains local target_toolchains = self:get("toolchains") if target_toolchains then toolchains = {} for _, name in ipairs(table.wrap(target_toolchains)) do local toolchain_opt = table.copy(self:extraconf("toolchains", name)) toolchain_opt.arch = self:arch() toolchain_opt.plat = self:plat() local toolchain_inst, errors = toolchain.load(name, toolchain_opt) -- attempt to load toolchain from project if not toolchain_inst and self._PROJECT then toolchain_inst = self._PROJECT.toolchain(name, toolchain_opt) end if not toolchain_inst then os.raise(errors) end table.insert(toolchains, toolchain_inst) end else -- load platform toolchains toolchains = self:platform():toolchains() end self:_memcache():set("toolchains", toolchains) end return toolchains end -- get the program and name of the given tool kind function _instance:tool(toolkind) return toolchain.tool(self:toolchains(), toolkind, {cachekey = "target_" .. self:name(), plat = self:plat(), arch = self:arch(), before_get = function() -- get program from set_toolchain/set_tools (deprecated) local program = self:get("toolset." .. toolkind) or self:get("toolchain." .. toolkind) if not program then local tools = self:get("tools") -- TODO: deprecated if tools then program = tools[toolkind] end end -- get program from `xmake f --cc` if not program and not self:get("toolchains") then program = config.get(toolkind) end return program end}) end -- get tool configuration from the toolchains function _instance:toolconfig(name) return toolchain.toolconfig(self:toolchains(), name, {cachekey = "target_" .. self:name(), plat = self:plat(), arch = self:arch(), after_get = function(toolchain_inst) -- get flags from target.on_xxflags() local script = toolchain_inst:get("target.on_" .. name) if type(script) == "function" then local ok, result_or_errors = utils.trycall(script, nil, self) if ok then return result_or_errors else os.raise(result_or_errors) end end end}) end -- get target apis function target.apis() return { values = { -- target.set_xxx "target.set_kind" , "target.set_plat" , "target.set_arch" , "target.set_strip" , "target.set_rules" , "target.set_group" , "target.set_version" , "target.set_license" , "target.set_enabled" , "target.set_default" , "target.set_options" , "target.set_symbols" , "target.set_filename" , "target.set_basename" , "target.set_extension" , "target.set_prefixname" , "target.set_suffixname" , "target.set_warnings" , "target.set_fpmodels" , "target.set_optimize" , "target.set_runtimes" , "target.set_languages" , "target.set_toolchains" -- target.add_xxx , "target.add_deps" , "target.add_rules" , "target.add_options" , "target.add_packages" , "target.add_imports" , "target.add_languages" , "target.add_vectorexts" , "target.add_toolchains" } , keyvalues = { -- target.set_xxx "target.set_values" , "target.set_configvar" , "target.set_runenv" , "target.set_toolchain" -- TODO: deprecated , "target.set_toolset" , "target.set_policy" -- target.add_xxx , "target.add_values" , "target.add_runenvs" } , paths = { -- target.set_xxx "target.set_targetdir" , "target.set_objectdir" , "target.set_dependir" , "target.set_configdir" , "target.set_installdir" , "target.set_rundir" -- target.add_xxx , "target.add_files" , "target.add_cleanfiles" , "target.add_configfiles" , "target.add_installfiles" -- target.del_xxx , "target.del_files" } , dictionary = { -- target.set_xxx "target.set_tools" -- TODO: deprecated , "target.add_tools" -- TODO: deprecated } , script = { -- target.on_xxx "target.on_run" , "target.on_load" , "target.on_config" , "target.on_link" , "target.on_build" , "target.on_build_file" , "target.on_build_files" , "target.on_clean" , "target.on_package" , "target.on_install" , "target.on_uninstall" -- target.before_xxx , "target.before_run" , "target.before_link" , "target.before_build" , "target.before_build_file" , "target.before_build_files" , "target.before_clean" , "target.before_package" , "target.before_install" , "target.before_uninstall" -- target.after_xxx , "target.after_run" , "target.after_link" , "target.after_build" , "target.after_build_file" , "target.after_build_files" , "target.after_clean" , "target.after_package" , "target.after_install" , "target.after_uninstall" } } end -- get the filename from the given target name and kind function target.filename(targetname, targetkind, opt) -- check opt = opt or {} assert(targetname and targetkind) -- make filename by format local filename = targetname local format = opt.format or platform.format(targetkind, opt.plat, opt.arch) if format then local splitinfo = format:split("$(name)", {plain = true, strict = true}) local prefixname = splitinfo[1] or "" local suffixname = "" local extension = splitinfo[2] or "" splitinfo = extension:split('.', {plain = true, limit = 2, strict = true}) if #splitinfo == 2 and splitinfo[1] ~= "" then suffixname = splitinfo[1] extension = "." .. splitinfo[2] end if opt.prefixname then prefixname = opt.prefixname end if opt.suffixname then suffixname = opt.suffixname end if opt.extension then extension = opt.extension end filename = prefixname .. targetname .. suffixname .. extension end return filename end -- get the link name of the target file function target.linkname(filename) local linkname, count = filename:gsub(target.filename("__pattern__", "static"):gsub("%.", "%%."):gsub("__pattern__", "(.+)") .. "$", "%1") if count == 0 then linkname, count = filename:gsub(target.filename("__pattern__", "shared"):gsub("%.", "%%."):gsub("__pattern__", "(.+)") .. "$", "%1") end if count == 0 then -- for the mingw/cross platform, it is compatible with the libxxx.a and xxx.lib local formats = {static = "lib$(name).a", shared = "lib$(name).so"} linkname, count = filename:gsub(target.filename("__pattern__", "static", {format = formats["static"]}):gsub("%.", "%%."):gsub("__pattern__", "(.+)") .. "$", "%1") if count == 0 then linkname, count = filename:gsub(target.filename("__pattern__", "shared", {format = formats["shared"]}):gsub("%.", "%%."):gsub("__pattern__", "(.+)") .. "$", "%1") end end return count > 0 and linkname or nil end -- new a target instance function target.new(...) return _instance.new(...) end -- return module return target