--!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-2020, TBOOX Open Source Group. -- -- @author ruki -- @file main.lua -- -- imports import("core.base.option") import("core.base.global") import("core.project.config") import("core.project.project") import("core.platform.platform") import("core.project.cache") import("lib.detect.cache", {alias = "detectcache"}) import("scangen") import("menuconf", {alias = "menuconf_show"}) import("configfiles", {alias = "generate_configfiles"}) import("configheader", {alias = "generate_configheader"}) import("actions.require.install", {alias = "install_requires", rootdir = os.programdir()}) -- filter option function _option_filter(name) local options = { target = true , file = true , root = true , yes = true , quiet = true , confirm = true , project = true , verbose = true , diagnosis = true , require = true } return not options[name] end -- host changed? function _host_changed(targetname) return os.host() ~= config.read("host", targetname) end -- need check function _need_check(changed) -- clean? if not changed then changed = option.get("clean") end -- get the current mtimes local mtimes = project.mtimes() -- get the previous mtimes local configcache = cache("local.config") if not changed then local mtimes_prev = configcache:get("mtimes") if mtimes_prev then -- check for all project files for file, mtime in pairs(mtimes) do -- modified? reconfig and rebuild it local mtime_prev = mtimes_prev[file] if not mtime_prev or mtime > mtime_prev then changed = true break end end end end -- update mtimes configcache:set("mtimes", mtimes) -- changed? return changed end -- check dependent target function _check_target_deps(target) -- check for _, depname in ipairs(target:get("deps")) do -- check dependent target name assert(depname ~= target:name(), "the target(%s) cannot depend self!", depname) -- get dependent target local deptarget = project.target(depname) -- check dependent target name assert(deptarget, "unknown target(%s) for %s.deps!", depname, target:name()) -- check the dependent targets _check_target_deps(deptarget) end end -- check target function _check_target(targetname) -- check assert(targetname) -- all? if targetname == "all" then -- check the dependent targets for _, target in pairs(project.targets()) do _check_target_deps(target) end else -- get target local target = project.target(targetname) -- check target name assert(target, "unknown target: %s", targetname) -- check the dependent targets _check_target_deps(target) end end -- main function main() -- avoid to run this task repeatly if _g.configured then return end _g.configured = true -- scan project and generate it if xmake.lua not exists local autogen = false local trybuild = option.get("trybuild") if not os.isfile(project.rootfile()) and not trybuild then autogen = utils.confirm({default = false, description = "xmake.lua not found, try generating it"}) if autogen then scangen() else os.exit() end end -- check the working directory if not option.get("project") and not option.get("file") and os.isdir(os.projectdir()) then if path.translate(os.projectdir()) ~= path.translate(os.workingdir()) then utils.warning([[You are working in the project directory(%s) and you can also force to build in current directory via run `xmake -P .`]], os.projectdir()) end end -- lock the whole project project.lock() -- enter menu config if option.get("menu") then menuconf_show() end -- the target name local targetname = option.get("target") or "all" -- get config cache local configcache = cache("local.config") -- load the project configure -- -- priority: option > option_cache > global > option_default > config_check > project_check > config_cache -- -- get the options local options = nil for name, value in pairs(option.options()) do if _option_filter(name) then options = options or {} options[name] = value end end -- override configure from the options or cache local options_changed = false local options_history = {} if not option.get("clean") and not autogen then options_history = configcache:get("options_" .. targetname) or {} options = options or options_history end for name, value in pairs(options) do -- options is changed by argument options? options_changed = options_changed or options_history[name] ~= value -- @note override it and mark as readonly (highest priority) config.set(name, value, {readonly = true}) end -- merge the cached configure -- -- @note we cannot load cache config when switching platform, arch .. -- so we need known whether options have been changed -- local configcache_loaded = false if not options_changed and not option.get("clean") and not _host_changed(targetname) then configcache_loaded = config.load(targetname) end -- merge the global configure for name, value in pairs(global.options()) do if config.get(name) == nil then config.set(name, value) end end -- merge the default options for name, value in pairs(option.defaults()) do if _option_filter(name) and config.get(name) == nil then config.set(name, value) end end -- merge the project options after default options for name, value in pairs(project.get("config")) do value = table.unwrap(value) assert(type(value) == "string" or type(value) == "boolean" or type(value) == "number", "set_config(%s): unsupported value type(%s)", name, type(value)) if not config.readonly(name) then config.set(name, value) end end -- merge the checked configure local recheck = _need_check(options_changed or not configcache_loaded or autogen) if recheck then -- clear detect cache detectcache.clear() -- check configure config.check() -- check project options project.check() end -- load platform platform.load(config.plat()) -- translate the build directory local buildir = config.get("buildir") if buildir and path.is_absolute(buildir) then config.set("buildir", path.relative(buildir, project.directory()), {readonly = true, force = true}) end -- only config for building project using third-party buildsystem if not trybuild then -- install and update requires and config header local require_enable = option.boolean(option.get("require")) if (recheck or require_enable) and require_enable ~= false then install_requires() end -- check target and ensure to load all targets, @note we must load targets after installing required packages, -- otherwise has_package() will be invalid. _check_target(targetname) -- update the config header if recheck then generate_configfiles() generate_configheader() end end -- dump config if option.get("verbose") then config.dump() end -- save options and configure for the given target config.save(targetname) configcache:set("options_" .. targetname, options) -- save options and configure for each targets if be all if not trybuild and targetname == "all" then for _, target in pairs(project.targets()) do config.save(target:name()) configcache:set("options_" .. target:name(), options) end end -- flush config cache configcache:flush() -- unlock the whole project project.unlock() end