--!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 main.lua -- -- imports import("core.base.option") import("core.base.global") import("core.base.hashset") import("core.tool.toolchain") import("core.project.config") import("core.project.project") import("core.platform.platform") import("private.detect.find_platform") import("core.cache.localcache") import("core.cache.detectcache") import("scangen") import("menuconf", {alias = "menuconf_show"}) import("configfiles", {alias = "generate_configfiles"}) import("private.action.require.register", {alias = "register_packages"}) import("private.action.require.install", {alias = "install_packages"}) import("private.service.remote_build.action", {alias = "remote_build_action"}) import("private.utils.target", {alias = "target_utils"}) -- 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 , export = true , import = true , check = true , menu = true } return not options[name] end -- host changed? function _host_changed() return os.host() ~= config.read("host") end -- need check function _need_check(changed) -- clean? if not changed then changed = option.get("clean") or option.get("check") end -- check for all project files local mtimes = project.mtimes() if not changed then local mtimes_prev = localcache.get("config", "mtimes") if mtimes_prev then 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 -- unfinished config/recheck if not changed and localcache.get("config", "recheck") then changed = true end -- Has xmake been updated? force to check config again -- we need to clean the dirty config cache of the old version if not changed then if os.mtime(path.join(os.programdir(), "core", "main.lua")) > os.mtime(config.filepath()) then changed = true os.touch(config.filepath(), {mtime = os.time()}) end end return changed end -- check target function _check_target(target, checked_targets) if not checked_targets[target:fullname()] then checked_targets[target:fullname()] = target for _, depname in ipairs(target:get("deps")) do local deptarget = project.target(depname, {namespace = target:namespace()}) assert(deptarget, "unknown target(%s) for %s.deps!", depname, target:fullname()) _check_target(deptarget, checked_targets) end end end -- check targets function _check_targets() assert(not project.is_loaded(), "project and targets may have been loaded early!") local checked_targets = {} for _, target in pairs(project.targets()) do _check_target(target, checked_targets) end end -- find default mode function _find_default_mode() local mode = config.mode() if not mode then mode = project.get("defaultmode") if not mode then mode = "release" end config.set("mode", mode) end return mode end -- check configs function _check_configs() -- check allowed modes local mode = config.mode() local allowed_modes = project.allowed_modes() if allowed_modes then if not allowed_modes:has(mode) then local allowed_modes_str = table.concat(allowed_modes:to_array(), ", ") raise("`%s` is not a valid complation mode for this project, please use one of %s", mode, allowed_modes_str) end end -- check allowed plats local plat = config.plat() local allowed_plats = project.allowed_plats() if allowed_plats then if not allowed_plats:has(plat) then local allowed_plats_str = table.concat(allowed_plats:to_array(), ", ") raise("`%s` is not a valid platform for this project, please use one of %s", plat, allowed_plats_str) end end -- check allowed archs local arch = config.arch() local allowed_archs = project.allowed_archs(config.plat()) if allowed_archs then if not allowed_archs:has(arch) then local allowed_archs_str = table.concat(allowed_archs:to_array(), ", ") raise("`%s` is not a valid complation arch for this project, please use one of %s", arch, allowed_archs_str) end end end -- export configs function _export_configs() local exportfile = option.get("export") if exportfile then config.save(exportfile, {public = true}) end end function main(opt) -- do action for remote? if remote_build_action.enabled() then return remote_build_action() end -- avoid running this task repeatly opt = opt or {} 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 -- no given project path not localcache.get("project", "projectdir") and -- no cached project path not localcache.get("project", "projectfile") and os.isdir(os.projectdir()) then if path.translate(os.projectdir()) ~= path.translate(os.workingdir()) then wprint([[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 local options_changed = false if option.get("menu") then options_changed = menuconf_show() end -- load the project configuration -- -- 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 -- merge options from the given import file local importfile = option.get("import") if importfile then assert(os.isfile(importfile), "%s not found!", importfile) -- we need to use readonly, @see https://github.com/xmake-io/xmake/issues/2278 local import_configs = io.load(importfile) if import_configs then for name, value in pairs(import_configs) do options = options or {} if options[name] == nil then options[name] = value end end end end -- override configuration from the options or cache local options_history = {} if not option.get("clean") and not autogen then options_history = localcache.get("config", "options") or {} options = options or options_history end for name, value in pairs(options) do -- Is options 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 configuration -- -- @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() then configcache_loaded = config.load() end -- merge the global configuration 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 local 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 -- find default mode local mode = _find_default_mode() assert(mode == config.mode()) -- find default platform and save to configuration local plat, arch = find_platform({global = true}) assert(plat == config.plat()) assert(arch == config.arch()) -- load platform instance local instance_plat = platform.load(plat, arch) -- merge the checked configuration local recheck = _need_check(options_changed or not configcache_loaded or autogen) if recheck then -- clear cached configuration if option.get("clean") then localcache.clear("config") end -- clear some local caches localcache.clear("detect") localcache.clear("option") localcache.clear("package") localcache.clear("toolchain") localcache.set("config", "recheck", true) if not opt.loadonly then localcache.save() end -- check platform instance_plat:check() -- check project options if not trybuild then project.check_options() end end -- translate the build directory local builddir = config.get("builddir") if config.get("buildir") then wprint("`xmake f --buildir=` has been deprecated, please use `xmake f -o/--builddir=`") builddir = config.get("buildir") config.set("builddir", builddir, {readonly = true, force = true}) end if builddir and path.is_absolute(builddir) then config.set("builddir", path.relative(builddir, project.directory()), {readonly = true, force = true}) end -- only config for building project using third-party buildsystem if not trybuild then -- check configs _check_configs() -- install and register packages local require_enable = option.boolean(option.get("require")) if (recheck or require_enable) then if require_enable ~= false then install_packages() else register_packages() end 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_targets() -- check target toolchains if recheck then target_utils.check_target_toolchains() end -- load targets project.load_targets({recheck = recheck}) -- update the config files generate_configfiles({force = recheck}) end -- dump config if option.get("verbose") and not opt.disable_dump then config.dump() end -- export configs if option.get("export") then _export_configs() end -- save configs and caches if not opt.loadonly then -- we need to save it and enable external working mode -- if we configure the given project directory -- -- @see https://github.com/xmake-io/xmake/issues/3342 -- local projectdir = option.get("project") local projectfile = option.get("file") if projectdir or projectfile then localcache.set("project", "projectdir", projectdir) localcache.set("project", "projectfile", projectfile) localcache.save("project") end -- save options and config cache localcache.set("config", "recheck", false) localcache.set("config", "mtimes", project.mtimes()) config.save() localcache.set("config", "options", options) localcache.save("config") -- save toolchain cache toolchain.save() -- save detect cache detectcache:save() end -- unlock the whole project project.unlock() end