--!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 process.lua -- -- define module: process local process = process or {} local _subprocess = _subprocess or {} -- load modules local io = require("base/io") local path = require("base/path") local utils = require("base/utils") local string = require("base/string") local coroutine = require("base/coroutine") local scheduler = require("base/scheduler") -- save original interfaces process._open = process._open or process.open process._openv = process._openv or process.openv process._wait = process._wait or process.wait process._kill = process._kill or process.kill process._close = process._close or process.close process.wait = nil process.kill = nil process.close = nil process._subprocess = _subprocess -- new an subprocess function _subprocess.new(program, proc) local subprocess = table.inherit(_subprocess) subprocess._PROGRAM = program subprocess._PROC = proc setmetatable(subprocess, _subprocess) return subprocess end -- get the process name -- -- @return the process name string -- function _subprocess:name() if not self._NAME then self._NAME = path.filename(self:program()) end return self._NAME end -- get the process program path -- -- @return the program path string -- function _subprocess:program() return self._PROGRAM end -- get cdata of process function _subprocess:cdata() return self._PROC end -- get poller object type, poller.OT_PROC function _subprocess:otype() return 3 end -- wait subprocess -- -- @param timeout the timeout -- -- @return ok, status -- function _subprocess:wait(timeout) -- ensure opened local ok, errors = self:_ensure_opened() if not ok then return -1, errors end -- wait events local result = -1 local status_or_errors = nil if scheduler:co_running() then result, status_or_errors = scheduler:poller_waitproc(self, timeout or -1) else result, status_or_errors = process._wait(self:cdata(), timeout or -1) end if result < 0 and status_or_errors then status_or_errors = string.format("%s: %s", self, status_or_errors) end return result, status_or_errors end -- kill the subprocess -- -- @return true on success -- function _subprocess:kill() -- ensure opened local ok, errors = self:_ensure_opened() if not ok then return false, errors end -- kill process process._kill(self:cdata()) return true end -- close the subprocess and release resources -- -- @return true on success -- function _subprocess:close() -- ensure opened local ok, errors = self:_ensure_opened() if not ok then return false, errors end -- cancel pipe events from the scheduler if scheduler:co_running() then ok, errors = scheduler:poller_cancel(self) if not ok then return false, errors end end -- close process ok = process._close(self:cdata()) if ok then self._PROC = nil end return ok end -- ensure the process is opened function _subprocess:_ensure_opened() if not self:cdata() then return false, string.format("%s: has been closed!", self) end return true end -- tostring(subprocess) function _subprocess:__tostring() return "" end -- gc(subprocess) function _subprocess:__gc() if self._PROC and process._close(self._PROC) then self._PROC = nil end end -- open a subprocess -- -- @param command the process command -- @param opt the option arguments, e.g. {stdin = filepath/file/pipe, stdout = filepath/file/pipe, stderr = filepath/file/pipe, envs = {"PATH=xxx", "XXX=yyy"}}) -- -- @return the subprocess -- function process.open(command, opt) -- get stdin and pass to subprocess opt = opt or {} local stdin = opt.stdin if type(stdin) == "string" then opt.inpath = stdin elseif type(stdin) == "table" then if stdin.otype and stdin:otype() == 2 then opt.inpipe = stdin:cdata() else opt.infile = stdin:cdata() end end -- get stdout and pass to subprocess local stdout = opt.stdout if type(stdout) == "string" then opt.outpath = stdout elseif type(stdout) == "table" then if stdout.otype and stdout:otype() == 2 then opt.outpipe = stdout:cdata() else opt.outfile = stdout:cdata() end end -- get stderr and pass to subprocess local stderr = opt.stderr if type(stderr) == "string" then opt.errpath = stderr elseif type(stderr) == "table" then if stderr.otype and stderr:otype() == 2 then opt.errpipe = stderr:cdata() else opt.errfile = stderr:cdata() end end -- open subprocess local proc = process._open(command, opt) if proc then return _subprocess.new(command:split(' ', {plain = true})[1], proc) else return nil, string.format("open process(%s) failed!", command) end end -- open a subprocess with the arguments list -- -- @param program the program -- @param argv the arguments list -- @param opt the option arguments, e.g. {stdin = filepath/file/pipe, stdout = filepath/file/pipe, stderr = filepath/file/pipe, envs = {"PATH=xxx", "XXX=yyy"}}) -- -- @return the subprocess -- function process.openv(program, argv, opt) -- get stdin and pass to subprocess opt = opt or {} local stdin = opt.stdin if type(stdin) == "string" then opt.inpath = stdin elseif type(stdin) == "table" then if stdin.otype and stdin:otype() == 2 then opt.inpipe = stdin:cdata() else opt.infile = stdin:cdata() end end -- get stdout and pass to subprocess local stdout = opt.stdout if type(stdout) == "string" then opt.outpath = stdout elseif type(stdout) == "table" then if stdout.otype and stdout:otype() == 2 then opt.outpipe = stdout:cdata() else opt.outfile = stdout:cdata() end end -- get stderr and pass to subprocess local stderr = opt.stderr if type(stderr) == "string" then opt.errpath = stderr elseif type(stderr) == "table" then if stderr.otype and stderr:otype() == 2 then opt.errpipe = stderr:cdata() else opt.errfile = stderr:cdata() end end -- open subprocess local proc = process._openv(program, argv, opt) if proc then return _subprocess.new(program, proc) else return nil, string.format("openv process(%s, %s) failed!", program, os.args(argv)) end end -- get missing dlls function process._get_missing_dlls(program) local missing = {} if not os.isexec(program) then return missing end -- get paths local pathenv = os.getenv("PATH") or "" local paths = path.splitenv(pathenv) table.insert(paths, 1, path.directory(program)) -- find missing dlls local sandbox_module = require("sandbox/modules/import/core/sandbox/module") local get_depend_libraries = sandbox_module.import("utils.binary.deplibs", {anonymous = true}) local imports = get_depend_libraries(program, {recursive = true}) or {} for i, dll in ipairs(imports) do imports[i] = path.filename(dll) end for _, dll in ipairs(imports) do local found = false for _, p in ipairs(paths) do if os.isfile(path.join(p, dll)) then found = true break end end if not found then table.insert(missing, dll) end end return missing end -- get process exit error message -- -- @param program the program path -- @param exitcode the exit code -- @return the error message string, or nil -- function process.get_exit_errors(program, exitcode) local errors if os.is_host("windows") then -- DLL is missing, 0xC0000135 if exitcode == -1073741515 then local missing_dlls = process._get_missing_dlls(program) if #missing_dlls > 0 then errors = string.format("system error 0xC0000135 (STATUS_DLL_NOT_FOUND).\nThe application failed to start because the following DLLs were not found:\n - %s\nPlease check your PATH environment variable or copy the missing DLLs to the executable directory.", table.concat(missing_dlls, "\n - ")) else errors = string.format("system error 0xC0000135 (STATUS_DLL_NOT_FOUND).\nThe application failed to start because a dependent DLL was not found.\nPlease check your PATH environment variable or copy the missing DLL to the executable directory.") end end end return errors end -- return module: process return process