--!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.task") import("core.project.config") import("core.base.global") import("core.project.project") import("core.platform.platform") import("devel.debugger") import("async.runjobs") import("private.action.run.runenvs") import("private.service.remote_build.action", {alias = "remote_build_action"}) import("private.detect.check_targetnames") import("lib.detect.find_tool") import("private.action.utils", {alias = "action_utils"}) function _run_wasi_target(targetfile, args, opt) opt = opt or {} local rundir = opt.rundir local addenvs = opt.addenvs local setenvs = opt.setenvs local wasmtime = find_tool("wasmtime") if wasmtime then local runargs = {targetfile} if args and #args > 0 then table.join2(runargs, args) end os.execv(wasmtime.program, runargs, { curdir = rundir, detach = option.get("detach"), addenvs = addenvs, setenvs = setenvs}) else raise("wasmtime not found, which is required for running wasi target!") end end function _run_wasm_target_in_browser(targetfile, opt) opt = opt or {} local rundir = opt.rundir local addenvs = opt.addenvs local setenvs = opt.setenvs -- prefer the .html file over .js for browser targets -- @see https://github.com/xmake-io/xmake/issues/7340 local htmlfile = targetfile:gsub("%.js$", ".html") if htmlfile ~= targetfile and os.isfile(htmlfile) then targetfile = htmlfile end local emrun = find_tool("emrun") if emrun then os.execv(emrun.program, {"--serve_root", path.directory(targetfile), targetfile}, { curdir = rundir, detach = option.get("detach"), addenvs = addenvs, setenvs = setenvs}) else local python = find_tool("python3") if not python then raise("emrun or python not found, which is required for running wasm target in browser!") end local url = "http://localhost:8000/" .. path.unix(path.relative(targetfile, rundir)) print("please open the url in browser") cprint("${color.success}%s${clear}", url) os.execv(python.program, {"-m", "http.server", "--bind", "127.0.0.1", "8000"}, { curdir = rundir, detach = option.get("detach"), addenvs = addenvs, setenvs = setenvs}) end end -- run target function _do_run_target(target) -- only for binary program if not target:is_binary() then return end -- get the run directory of target local rundir = target:rundir() -- get the absolute target file path local targetfile = path.absolute(target:targetfile()) -- get the run environments local addenvs, setenvs = runenvs.make(target) -- get run arguments local args = table.wrap(option.get("arguments") or target:get("runargs")) -- run wasm target if target:is_plat("wasm") then if target:has_tool("cc", "emcc") then _run_wasm_target_in_browser(targetfile, {rundir = rundir, addenvs = addenvs, setenvs = setenvs}) else _run_wasi_target(targetfile, args, {rundir = rundir, addenvs = addenvs, setenvs = setenvs}) end return end -- run windows target on non-windows host via wine if not is_host("windows") and target:is_plat("windows") then local wine = assert(find_tool("wine"), "wine not found!") table.insert(args, 1, targetfile) targetfile = wine.program end -- enable GUI error dialogs (Windows only) -- @see https://github.com/xmake-io/xmake/issues/7176 local old_errormode if target:policy("run.windows_error_dialog") and winos.set_error_mode then old_errormode = winos.set_error_mode(0) end -- debugging? if option.get("debug") then debugger.run(targetfile, args, {curdir = rundir, addenvs = addenvs, setenvs = setenvs}) else os.execv(targetfile, args, {curdir = rundir, detach = option.get("detach"), addenvs = addenvs, setenvs = setenvs}) end -- restore error mode if old_errormode then winos.set_error_mode(old_errormode) end end -- run target function _on_run_target(target) -- build target with rules local done = false for _, r in ipairs(target:orderules()) do local on_run = r:script("run") if on_run then on_run(target) done = true end end if done then return end -- do run _do_run_target(target) end -- recursively add target envs function _add_target_pkgenvs(target, targets_added) if targets_added[target:name()] then return end targets_added[target:name()] = true os.addenvs(target:pkgenvs()) for _, dep in ipairs(target:orderdeps()) do _add_target_pkgenvs(dep, targets_added) end end -- run the given target function _run(target) -- has been disabled? if not target:is_enabled() then return end -- enter the environments of the target packages local oldenvs = os.getenvs() _add_target_pkgenvs(target, {}) -- the target scripts local scripts = { target:script("run_before") , function (target) for _, r in ipairs(target:orderules()) do local before_run = r:script("run_before") if before_run then before_run(target) end end end , target:script("run", _on_run_target) , function (target) for _, r in ipairs(target:orderules()) do local after_run = r:script("run_after") if after_run then after_run(target) end end end , target:script("run_after") } -- run the target scripts for i = 1, 5 do local script = scripts[i] if script ~= nil then script(target) end end -- leave the environments of the target packages os.setenvs(oldenvs) end -- check if the target is runnable function _is_runnable(target) if target:is_binary() or target:script("run") then return true end for _, r in ipairs(target:orderules()) do if r:script("run") then return true end end end -- check targets function _check_targets(targetname, group_pattern) -- get targets local targets = {} if targetname then local target = assert(check_targetnames(targetname)) table.insert(targets, target) else for _, target in ipairs(project.ordertargets()) do if _is_runnable(target) then local group = target:get("group") if (target:is_default() and not group_pattern) or option.get("all") or (group_pattern and group and group:match(group_pattern)) then table.insert(targets, target) end end end end -- filter and check targets with builtin-run script local targetnames = {} for _, target in ipairs(targets) do if target:targetfile() and target:is_enabled() and not target:script("run") then local targetfile = target:targetfile() if targetfile and not os.isfile(targetfile) then table.insert(targetnames, target:name()) end end end -- there are targets that have not yet been built? if #targetnames > 0 then raise("please run `$xmake build [target]` to build the following targets first:\n -> " .. table.concat(targetnames, '\n -> ')) end end -- main function main() -- do action for remote? if remote_build_action.enabled() then return remote_build_action() end -- load config first config.load() -- Automatically build before running if project.policy("run.autobuild") then -- we need clear the previous config and reload it -- to avoid trigger recheck configs config.clear() task.run("build", {target = option.get("target"), all = option.get("all")}, {disable_dump = true}) else -- it will call on_config, we need to avoid repeat call it when autobuild is enabled. project.load_targets() end -- check targets first local targetname, group_pattern = action_utils.get_target_and_group() _check_targets(targetname, group_pattern) -- enter project directory local oldir = os.cd(project.directory()) -- run the given target? if targetname then _run(project.target(targetname)) else local targets = {} for _, target in ipairs(project.ordertargets()) do if _is_runnable(target) then local group = target:get("group") if (target:is_default() and not group_pattern) or option.get("all") or (group_pattern and group and group:match(group_pattern)) then table.insert(targets, target) end end end local jobs = tonumber(option.get("jobs") or "1") runjobs("run_targets", function (index) local target = targets[index] if target then _run(target) end end, {total = #targets, comax = jobs, isolate = true}) end -- leave project directory os.cd(oldir) end