diff options
| author | ruki <[email protected]> | 2022-07-23 13:42:43 +0800 |
|---|---|---|
| committer | GitHub <[email protected]> | 2022-07-23 13:42:43 +0800 |
| commit | fad741419fd216e5057bd4c972fc324f16ade47b (patch) | |
| tree | 2665c936e0f7550f78ce975bb4b7cb2175de1456 | |
| parent | f84814e6c98db75e538007cebd7edee34710b275 (diff) | |
| parent | c6fb2dcb124ead33156e2639423ed7ae9c19dbfa (diff) | |
Merge pull request #2592 from xmake-io/fwatcher
add fs watcher
26 files changed, 1088 insertions, 25 deletions
diff --git a/core/plat/macosx/prefix.mak b/core/plat/macosx/prefix.mak index 15bf938b9..706772631 100644 --- a/core/plat/macosx/prefix.mak +++ b/core/plat/macosx/prefix.mak @@ -130,7 +130,7 @@ MMFLAGS = LDFLAGS_ARCH := $(if $(findstring arm64,$(BUILD_ARCH)),,-pagezero_size 10000 -image_base 100000000) LDFLAGS_RELEASE = -flto LDFLAGS_DEBUG = -LDFLAGS = -m$(BITS) -all_load $(LDFLAGS_ARCH) -mmacosx-version-min=10.7 +LDFLAGS = -m$(BITS) -all_load $(LDFLAGS_ARCH) -mmacosx-version-min=10.7 -framework CoreFoundation -framework CoreServices LDFLAGS-L = -L LDFLAGS-l = -l LDFLAGS-f = diff --git a/core/src/tbox/inc/linux/tbox.config.h b/core/src/tbox/inc/linux/tbox.config.h index d3e16a6a3..b5e0faa7f 100755 --- a/core/src/tbox/inc/linux/tbox.config.h +++ b/core/src/tbox/inc/linux/tbox.config.h @@ -203,4 +203,7 @@ // valgrind functions /* #undef TB_CONFIG_SYSTEMV_HAVE_VALGRIND_STACK_REGISTER */ +// linux functions +#define TB_CONFIG_LINUX_HAVE_INOTIFY_INIT 1 + #endif diff --git a/core/src/tbox/makefile b/core/src/tbox/makefile index f24303eb1..b03269a78 100644 --- a/core/src/tbox/makefile +++ b/core/src/tbox/makefile @@ -229,6 +229,7 @@ tbox_C_FILES += \ tbox/src/tbox/platform/print \ tbox/src/tbox/platform/stdfile \ tbox/src/tbox/platform/process \ + tbox/src/tbox/platform/fwatcher \ tbox/src/tbox/platform/cpu \ tbox/src/tbox/platform/sched \ tbox/src/tbox/platform/semaphore \ diff --git a/core/src/tbox/tbox b/core/src/tbox/tbox -Subproject 0cdd892038242128bbefa4f00dcb4cc451c751d +Subproject 138ea23adf94e2ef4f7db7984fe432872abf3b5 diff --git a/core/src/xmake/engine.c b/core/src/xmake/engine.c index d275c21f8..7c904fb38 100644 --- a/core/src/xmake/engine.c +++ b/core/src/xmake/engine.c @@ -245,6 +245,13 @@ tb_int_t xm_process_wait(lua_State* lua); tb_int_t xm_process_kill(lua_State* lua); tb_int_t xm_process_close(lua_State* lua); +// the fwatcher functions +tb_int_t xm_fwatcher_open(lua_State* lua); +tb_int_t xm_fwatcher_add(lua_State* lua); +tb_int_t xm_fwatcher_remove(lua_State* lua); +tb_int_t xm_fwatcher_wait(lua_State* lua); +tb_int_t xm_fwatcher_close(lua_State* lua); + // the sandbox functions tb_int_t xm_sandbox_interactive(lua_State* lua); @@ -488,6 +495,17 @@ static luaL_Reg const g_process_functions[] = , { tb_null, tb_null } }; +// the fwatcher functions +static luaL_Reg const g_fwatcher_functions[] = +{ + { "open", xm_fwatcher_open } +, { "add", xm_fwatcher_add } +, { "remove", xm_fwatcher_remove } +, { "wait", xm_fwatcher_wait } +, { "close", xm_fwatcher_close } +, { tb_null, tb_null } +}; + // the sandbox functions static luaL_Reg const g_sandbox_functions[] = { @@ -1025,6 +1043,9 @@ xm_engine_ref_t xm_engine_init(tb_char_t const* name, xm_engine_lni_initalizer_c // bind process functions xm_lua_register(engine->lua, "process", g_process_functions); + // bind fwatcher functions + xm_lua_register(engine->lua, "fwatcher", g_fwatcher_functions); + // bind sandbox functions xm_lua_register(engine->lua, "sandbox", g_sandbox_functions); diff --git a/core/src/xmake/fwatcher/add.c b/core/src/xmake/fwatcher/add.c new file mode 100644 index 000000000..a71ce83f1 --- /dev/null +++ b/core/src/xmake/fwatcher/add.c @@ -0,0 +1,64 @@ +/*!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 add.c + * + */ + +/* ////////////////////////////////////////////////////////////////////////////////////// + * trace + */ +#define TB_TRACE_MODULE_NAME "fwatcher.add" +#define TB_TRACE_MODULE_DEBUG (0) + +/* ////////////////////////////////////////////////////////////////////////////////////// + * includes + */ +#include "prefix.h" + +/* ////////////////////////////////////////////////////////////////////////////////////// + * implementation + */ + +// fwatcher.add(watchdir, recursion) +tb_int_t xm_fwatcher_add(lua_State* lua) +{ + // check + tb_assert_and_check_return_val(lua, 0); + + // is pointer? + if (!xm_lua_ispointer(lua, 1)) + return 0; + + // get the fwatcher + tb_fwatcher_ref_t fwatcher = (tb_fwatcher_ref_t)xm_lua_topointer(lua, 1); + tb_check_return_val(fwatcher, 0); + + // get watchdir + tb_char_t const* watchdir = luaL_checkstring(lua, 2); + tb_check_return_val(watchdir, 0); + + // get recursion + tb_bool_t recursion = lua_toboolean(lua, 3); + + // add watchdir + tb_bool_t ok = tb_fwatcher_add(fwatcher, watchdir, recursion); + + // save result + lua_pushboolean(lua, ok); + return 1; +} diff --git a/core/src/xmake/fwatcher/close.c b/core/src/xmake/fwatcher/close.c new file mode 100644 index 000000000..0430afa00 --- /dev/null +++ b/core/src/xmake/fwatcher/close.c @@ -0,0 +1,57 @@ +/*!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 close.c + * + */ + +/* ////////////////////////////////////////////////////////////////////////////////////// + * trace + */ +#define TB_TRACE_MODULE_NAME "fwatcher.close" +#define TB_TRACE_MODULE_DEBUG (0) + +/* ////////////////////////////////////////////////////////////////////////////////////// + * includes + */ +#include "prefix.h" + +/* ////////////////////////////////////////////////////////////////////////////////////// + * implementation + */ + +// fwatcher.close(p) +tb_int_t xm_fwatcher_close(lua_State* lua) +{ + // check + tb_assert_and_check_return_val(lua, 0); + + // is pointer? + if (!xm_lua_ispointer(lua, 1)) + return 0; + + // get the fwatcher + tb_fwatcher_ref_t fwatcher = (tb_fwatcher_ref_t)xm_lua_topointer(lua, 1); + tb_check_return_val(fwatcher, 0); + + // exit fwatcher + tb_fwatcher_exit(fwatcher); + + // save result: ok + lua_pushboolean(lua, tb_true); + return 1; +} diff --git a/core/src/xmake/fwatcher/open.c b/core/src/xmake/fwatcher/open.c new file mode 100644 index 000000000..431762922 --- /dev/null +++ b/core/src/xmake/fwatcher/open.c @@ -0,0 +1,46 @@ +/*!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 open.c + * + */ + +/* ////////////////////////////////////////////////////////////////////////////////////// + * trace + */ +#define TB_TRACE_MODULE_NAME "fwatcher.open" +#define TB_TRACE_MODULE_DEBUG (0) + +/* ////////////////////////////////////////////////////////////////////////////////////// + * includes + */ +#include "prefix.h" + +/* ////////////////////////////////////////////////////////////////////////////////////// + * implementation + */ +tb_int_t xm_fwatcher_open(lua_State* lua) +{ + // check + tb_assert_and_check_return_val(lua, 0); + + // init fwatcher + tb_fwatcher_ref_t fwatcher = (tb_fwatcher_ref_t)tb_fwatcher_init(); + if (fwatcher) xm_lua_pushpointer(lua, (tb_pointer_t)fwatcher); + else lua_pushnil(lua); + return 1; +} diff --git a/core/src/xmake/fwatcher/prefix.h b/core/src/xmake/fwatcher/prefix.h new file mode 100644 index 000000000..b203a555e --- /dev/null +++ b/core/src/xmake/fwatcher/prefix.h @@ -0,0 +1,31 @@ +/*!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 prefix.h + * + */ +#ifndef XM_FWATCHER_PREFIX_H +#define XM_FWATCHER_PREFIX_H + +/* ////////////////////////////////////////////////////////////////////////////////////// + * includes + */ +#include "../prefix.h" + +#endif + + diff --git a/core/src/xmake/fwatcher/remove.c b/core/src/xmake/fwatcher/remove.c new file mode 100644 index 000000000..334e0a938 --- /dev/null +++ b/core/src/xmake/fwatcher/remove.c @@ -0,0 +1,62 @@ +/*!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 remove.c + * + */ + +/* ////////////////////////////////////////////////////////////////////////////////////// + * trace + */ +#define TB_TRACE_MODULE_NAME "fwatcher.remove" +#define TB_TRACE_MODULE_DEBUG (0) + +/* ////////////////////////////////////////////////////////////////////////////////////// + * includes + */ +#include "prefix.h" + +/* ////////////////////////////////////////////////////////////////////////////////////// + * implementation + */ + +// fwatcher.remove(watchdir) +tb_int_t xm_fwatcher_remove(lua_State* lua) +{ + // check + tb_assert_and_check_return_val(lua, 0); + + // is pointer? + if (!xm_lua_ispointer(lua, 1)) + return 0; + + // get the fwatcher + tb_fwatcher_ref_t fwatcher = (tb_fwatcher_ref_t)xm_lua_topointer(lua, 1); + tb_check_return_val(fwatcher, 0); + + // get watchdir + tb_char_t const* watchdir = luaL_checkstring(lua, 2); + tb_check_return_val(watchdir, 0); + + // remove watchdir + tb_bool_t ok = tb_fwatcher_remove(fwatcher, watchdir); + + // save result + lua_pushboolean(lua, ok); + return 1; +} + diff --git a/core/src/xmake/fwatcher/wait.c b/core/src/xmake/fwatcher/wait.c new file mode 100644 index 000000000..f8bbd6b78 --- /dev/null +++ b/core/src/xmake/fwatcher/wait.c @@ -0,0 +1,73 @@ +/*!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 wait.c + * + */ + +/* ////////////////////////////////////////////////////////////////////////////////////// + * trace + */ +#define TB_TRACE_MODULE_NAME "fwatcher.wait" +#define TB_TRACE_MODULE_DEBUG (0) + +/* ////////////////////////////////////////////////////////////////////////////////////// + * includes + */ +#include "prefix.h" + +/* ////////////////////////////////////////////////////////////////////////////////////// + * implementation + */ + +// fwatcher.wait(p) +tb_int_t xm_fwatcher_wait(lua_State* lua) +{ + // check + tb_assert_and_check_return_val(lua, 0); + + // is pointer? + if (!xm_lua_ispointer(lua, 1)) + return 0; + + // get the fwatcher + tb_fwatcher_ref_t fwatcher = (tb_fwatcher_ref_t)xm_lua_topointer(lua, 1); + tb_check_return_val(fwatcher, 0); + + // get the timeout + tb_long_t timeout = (tb_long_t)luaL_checkinteger(lua, 2); + + // wait fwatcher event + tb_fwatcher_event_t event; + tb_long_t ok = tb_fwatcher_wait(fwatcher, &event, timeout); + + // save result + lua_pushinteger(lua, ok); + if (ok > 0) + { + lua_newtable(lua); + lua_pushstring(lua, "path"); + lua_pushstring(lua, event.filepath); + lua_settable(lua, -3); + + lua_pushstring(lua, "type"); + lua_pushinteger(lua, event.event); + lua_settable(lua, -3); + return 2; + } + return 1; +} diff --git a/core/src/xmake/io/poller_wait.c b/core/src/xmake/io/poller_wait.c index 008f1366e..7389faacf 100644 --- a/core/src/xmake/io/poller_wait.c +++ b/core/src/xmake/io/poller_wait.c @@ -54,7 +54,22 @@ static tb_void_t xm_io_poller_event(tb_poller_ref_t poller, tb_poller_object_ref if (priv) lua_pushstring(g_lua, (tb_char_t const*)priv); else lua_pushlightuserdata(g_lua, object->ref.ptr); lua_rawseti(g_lua, -2, 2); - lua_pushinteger(g_lua, (tb_int_t)events); + if (object->type == TB_POLLER_OBJECT_FWATCHER) + { + lua_newtable(g_lua); + tb_fwatcher_event_t* event = (tb_fwatcher_event_t*)events; + if (event) + { + lua_pushstring(g_lua, "path"); + lua_pushstring(g_lua, event->filepath); + lua_settable(g_lua, -3); + + lua_pushstring(g_lua, "type"); + lua_pushinteger(g_lua, event->event); + lua_settable(g_lua, -3); + } + } + else lua_pushinteger(g_lua, (tb_int_t)events); lua_rawseti(g_lua, -2, 3); lua_rawseti(g_lua, -2, ++g_events_count); } diff --git a/core/src/xmake/makefile b/core/src/xmake/makefile index 996b76578..5d017f0b0 100644 --- a/core/src/xmake/makefile +++ b/core/src/xmake/makefile @@ -115,6 +115,11 @@ xmake_C_FILES += \ process/wait \ process/kill \ process/close \ + fwatcher/open \ + fwatcher/add \ + fwatcher/remove \ + fwatcher/wait \ + fwatcher/close \ sandbox/interactive \ semver/parse \ semver/compare \ diff --git a/tests/modules/fwatcher/on_created.lua b/tests/modules/fwatcher/on_created.lua new file mode 100644 index 000000000..3dee56635 --- /dev/null +++ b/tests/modules/fwatcher/on_created.lua @@ -0,0 +1,8 @@ +import("core.base.fwatcher") + +function main(watchdir) + print("watch %s ..", watchdir) + fwatcher.on_created(watchdir, function (filepath) + print(filepath, "created") + end) +end diff --git a/tests/modules/fwatcher/on_deleted.lua b/tests/modules/fwatcher/on_deleted.lua new file mode 100644 index 000000000..b69757157 --- /dev/null +++ b/tests/modules/fwatcher/on_deleted.lua @@ -0,0 +1,8 @@ +import("core.base.fwatcher") + +function main(watchdir) + print("watch %s ..", watchdir) + fwatcher.on_deleted(watchdir, function (filepath) + print(filepath, "deleted") + end) +end diff --git a/tests/modules/fwatcher/on_modified.lua b/tests/modules/fwatcher/on_modified.lua new file mode 100644 index 000000000..834c182bf --- /dev/null +++ b/tests/modules/fwatcher/on_modified.lua @@ -0,0 +1,8 @@ +import("core.base.fwatcher") + +function main(watchdir) + print("watch %s ..", watchdir) + fwatcher.on_modified(watchdir, function (filepath) + print(filepath, "modified") + end) +end diff --git a/tests/modules/fwatcher/sched_watchdir.lua b/tests/modules/fwatcher/sched_watchdir.lua new file mode 100644 index 000000000..e990236ab --- /dev/null +++ b/tests/modules/fwatcher/sched_watchdir.lua @@ -0,0 +1,25 @@ +import("core.base.fwatcher") +import("core.base.scheduler") + +function _watch(watchdir) + print("watch %s ..", watchdir) + fwatcher.add(watchdir) + while true do + local ok, event = fwatcher.wait(-1) + if ok > 0 then + print(event) + end + end +end + +function _sleep() + while true do + print("sleep ..") + os.sleep(1000) + end +end + +function main(watchdir) + scheduler.co_start(_watch, watchdir) + scheduler.co_start(_sleep) +end diff --git a/tests/modules/fwatcher/watchdir.lua b/tests/modules/fwatcher/watchdir.lua new file mode 100644 index 000000000..3bd8e8897 --- /dev/null +++ b/tests/modules/fwatcher/watchdir.lua @@ -0,0 +1,12 @@ +import("core.base.fwatcher") + +function main(watchdir) + print("watch %s ..", watchdir) + fwatcher.add(watchdir) + while true do + local ok, event = fwatcher.wait(-1) + if ok > 0 then + print(event) + end + end +end diff --git a/tests/modules/fwatcher/watchdirs.lua b/tests/modules/fwatcher/watchdirs.lua new file mode 100644 index 000000000..9d3b81add --- /dev/null +++ b/tests/modules/fwatcher/watchdirs.lua @@ -0,0 +1,16 @@ +import("core.base.fwatcher") + +function main(watchdir) + print("watch %s ..", watchdir) + fwatcher.watchdirs(watchdir, function (event) + local status + if event.type == fwatcher.ET_CREATE then + status = "created" + elseif event.type == fwatcher.ET_MODIFY then + status = "modified" + elseif event.type == fwatcher.ET_DELETE then + status = "deleted" + end + print(event.path, status) + end) +end diff --git a/xmake/core/base/fwatcher.lua b/xmake/core/base/fwatcher.lua new file mode 100644 index 000000000..ddff66a06 --- /dev/null +++ b/xmake/core/base/fwatcher.lua @@ -0,0 +1,280 @@ +--!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 fwatcher.lua +-- + +-- define module: fwatcher +local fwatcher = fwatcher or {} +local _instance = _instance or {} + +-- load modules +local os = require("base/os") +local string = require("base/string") +local coroutine = require("base/coroutine") +local scheduler = require("base/scheduler") + +-- save original interfaces +fwatcher._open = fwatcher._open or fwatcher.open +fwatcher._add = fwatcher._add or fwatcher.add +fwatcher._remove = fwatcher._remove or fwatcher.remove +fwatcher._wait = fwatcher._wait or fwatcher.wait +fwatcher._close = fwatcher._close or fwatcher.close + +-- the fwatcher event type, @see tbox/platform/fwatcher.h +fwatcher.ET_MODIFY = 1 +fwatcher.ET_CREATE = 2 +fwatcher.ET_DELETE = 4 + +-- get cdata of fwatcher +function _instance:cdata() + local cdata = self._CDATA + if not cdata and not self._CLOSED then + cdata = fwatcher._open() + self._CDATA = cdata + end + return cdata +end + +-- get poller object type, poller.OT_FWATCHER +function _instance:otype() + return 4 +end + +-- add watch directory, e.g. {recursion = true} +function _instance:add(watchdir, opt) + + -- ensure opened + local ok, errors = self:_ensure_opened() + if not ok then + return false, errors + end + + -- add watchdir + opt = opt or {} + local ok, errors = fwatcher._add(self:cdata(), watchdir, opt.recursion or false) + if not ok then + errors = string.format("<fwatcher>: add %s failed, %s", watchdir, errors or "unknown errors") + end + return ok, errors +end + +-- remove watch directory +function _instance:remove(watchdir) + + -- ensure opened + local ok, errors = self:_ensure_opened() + if not ok then + return false, errors + end + + -- remove watchdir + opt = opt or {} + local ok, errors = fwatcher._remove(self:cdata(), watchdir) + if not ok then + errors = string.format("<fwatcher>: remove %s failed, %s", watchdir, errors or "unknown errors") + end + return ok, errors +end + +-- wait event +-- +-- @param timeout the timeout +-- +-- @return ok, event, e.g {type = fwatcher.ET_MODIFY, path = "/tmp"} +-- +function _instance:wait(timeout) + + -- ensure opened + local ok, errors = self:_ensure_opened() + if not ok then + return -1, errors + end + + -- wait events + local result = -1 + local event_or_errors = nil + if scheduler:co_running() then + result, event_or_errors = scheduler:poller_waitfs(self, timeout or -1) + else + result, event_or_errors = fwatcher._wait(self:cdata(), timeout or -1) + end + if result < 0 and event_or_errors then + event_or_errors = string.format("<fwatcher>: wait failed, %s", event_or_errors) + end + return result, event_or_errors +end + +-- close instance +function _instance: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 fwatcher + ok = fwatcher._close(self:cdata()) + if ok then + self._CDATA = nil + self._CLOSED = true + end + return ok +end + +-- ensure the fwatcher is opened +function _instance:_ensure_opened() + if not self:cdata() then + return false, string.format("%s: has been closed!", self) + end + return true +end + +-- add watchdir +function fwatcher.add(watchdir, opt) + return _instance:add(watchdir, opt) +end + +-- remove watchdir +function fwatcher.remove(watchdir) + return _instance:remove(watchdir) +end + +-- wait event +function fwatcher.wait(timeout) + return _instance:wait(timeout) +end + +-- watch directories +-- +-- @param watchdirs the watch directories, pattern path string or path list +-- @param callback the event callback +-- @param opt the option, e.g. {timeout = -1, recursion = true} +-- +-- @code +-- fwatcher.watchdirs("/tmp/test_*", function (event) +-- print(event) +-- end, {timeout = -1, recursion = true}) +-- @endcode +function fwatcher.watchdirs(watchdirs, callback, opt) + + -- add watch directories + opt = opt or {} + if type(watchdirs) == "string" then + watchdirs = os.dirs(watchdirs) + end + local ok = true + local errors = nil + for _, watchdir in ipairs(watchdirs) do + ok, errors = fwatcher.add(watchdir, opt) + if not ok then + break + end + end + + -- do watch + while ok do + local result, event_or_errors = fwatcher.wait(opt.timeout or -1) + if result < 0 then + ok = false + errors = event_or_errors + break + end + if result > 0 then + callback(event_or_errors) + end + end + + -- remove watch directories + for _, watchdir in ipairs(watchdirs) do + local result, rm_errors = fwatcher.remove(watchdir) + if not result then + ok = false + errors = errors or rm_errors + break + end + end + return ok, errors +end + +-- watch the created file path in directories +-- +-- @param watchdirs the watch directories, pattern path string or path list +-- @param callback the event callback +-- @param opt the option, e.g. {timeout = -1, recursion = true} +-- +-- @code +-- fwatcher.on_created("/tmp/test_*", function (filepath) +-- print(filepath) +-- end, {timeout = -1, recursion = true}) +-- @endcode +function fwatcher.on_created(watchdirs, callback, opt) + return fwatcher.watchdirs(watchdirs, function (event) + if event and event.type == fwatcher.ET_CREATE then + callback(event.path) + end + end, opt) +end + +-- watch the modified file path in directories +-- +-- @param watchdirs the watch directories, pattern path string or path list +-- @param callback the event callback +-- @param opt the option, e.g. {timeout = -1, recursion = true} +-- +-- @code +-- fwatcher.on_modified("/tmp/test_*", function (filepath) +-- print(filepath) +-- end, {timeout = -1, recursion = true}) +-- @endcode +function fwatcher.on_modified(watchdirs, callback, opt) + return fwatcher.watchdirs(watchdirs, function (event) + if event and event.type == fwatcher.ET_MODIFY then + callback(event.path) + end + end, opt) +end + +-- watch the deleted file path in directories +-- +-- @param watchdirs the watch directories, pattern path string or path list +-- @param callback the event callback +-- @param opt the option, e.g. {timeout = -1, recursion = true} +-- +-- @code +-- fwatcher.on_deleted("/tmp/test_*", function (filepath) +-- print(filepath) +-- end, {timeout = -1, recursion = true}) +-- @endcode +function fwatcher.on_deleted(watchdirs, callback, opt) + return fwatcher.watchdirs(watchdirs, function (event) + if event and event.type == fwatcher.ET_DELETE then + callback(event.path) + end + end, opt) +end + +return fwatcher diff --git a/xmake/core/base/poller.lua b/xmake/core/base/poller.lua index f8e216f26..894e780c2 100644 --- a/xmake/core/base/poller.lua +++ b/xmake/core/base/poller.lua @@ -26,9 +26,10 @@ local io = require("base/io") local string = require("base/string") -- the poller object type, @see tbox/platform/poller.h -poller.OT_SOCK = 1 -poller.OT_PIPE = 2 -poller.OT_PROC = 3 +poller.OT_SOCK = 1 +poller.OT_PIPE = 2 +poller.OT_PROC = 3 +poller.OT_FWATCHER = 4 -- the poller events, @see tbox/platform/poller.h poller.EV_POLLER_RECV = 1 diff --git a/xmake/core/base/scheduler.lua b/xmake/core/base/scheduler.lua index d961c1c75..ae8014dce 100644 --- a/xmake/core/base/scheduler.lua +++ b/xmake/core/base/scheduler.lua @@ -144,7 +144,7 @@ function scheduler:_timer() return t end --- get poller object data for socket, pipe or process object +-- get poller object data for socket, pipe, process, fwatcher object function scheduler:_poller_data(obj) return self._POLLERDATA and self._POLLERDATA[obj] or nil end @@ -196,19 +196,19 @@ function scheduler:_poller_events_cb(obj, events) return false, string.format("%s: cannot get poller data!", obj) end - -- is process object? - if obj:otype() == poller.OT_PROC then + -- is process/fwatcher object? + if obj:otype() == poller.OT_PROC or obj:otype() == poller.OT_FWATCHER then - -- resume coroutine and return the process exit status - pollerdata.proc_status = events + -- resume coroutine and return the process exit status/fwatcher event + pollerdata.object_event = events - -- waiting process? resume this coroutine + -- waiting process/fwatcher? resume this coroutine if pollerdata.co_waiting then local co_waiting = pollerdata.co_waiting pollerdata.co_waiting = nil return self:_poller_resume_co(co_waiting, 1) else - pollerdata.proc_pending = 1 + pollerdata.object_pending = 1 end return true end @@ -745,14 +745,14 @@ function scheduler:poller_waitproc(obj, timeout) -- get and allocate poller object data local pollerdata = self:_poller_data(obj) if not pollerdata then - pollerdata = {proc_pending = 0, proc_status = 0} + pollerdata = {object_pending = 0, object_event = 0} self:_poller_data_set(obj, pollerdata) end -- has pending process status? - if pollerdata.proc_pending ~= 0 then - pollerdata.proc_pending = 0 - return 1, pollerdata.proc_status + if pollerdata.object_pending ~= 0 then + pollerdata.object_pending = 0 + return 1, pollerdata.object_event end -- insert poller object to poller for waiting process @@ -776,8 +776,8 @@ function scheduler:poller_waitproc(obj, timeout) running:_timer_task_set(timer_task) -- set process status - pollerdata.proc_status = 0 - pollerdata.proc_pending = 0 + pollerdata.object_event = 0 + pollerdata.object_pending = 0 pollerdata.co_waiting = running -- save the waiting poller object @@ -785,7 +785,73 @@ function scheduler:poller_waitproc(obj, timeout) -- wait local ok = self:co_suspend() - return ok, pollerdata.proc_status + return ok, pollerdata.object_event +end + +-- wait poller object/fwatcher status +function scheduler:poller_waitfs(obj, timeout) + + -- get the running coroutine + local running = self:co_running() + if not running then + return -1, "we must call poller_wait() in coroutine with scheduler!" + end + + -- is stopped? + if not self._STARTED then + return -1, "the scheduler is stopped!" + end + + -- check the object type + local otype = obj:otype() + if otype ~= poller.OT_FWATCHER then + return -1, string.format("%s: invalid object type(%d)!", obj, otype) + end + + -- get and allocate poller object data + local pollerdata = self:_poller_data(obj) + if not pollerdata then + pollerdata = {object_pending = 0, object_event = 0} + self:_poller_data_set(obj, pollerdata) + end + + -- has pending process status? + if pollerdata.object_pending ~= 0 then + pollerdata.object_pending = 0 + return 1, pollerdata.object_event + end + + -- insert poller object to poller for waiting fwatcher + local ok, errors = poller:insert(obj, 0, self._poller_events_cb) + if not ok then + return -1, errors + end + + -- register timeout task to timer + local timer_task = nil + if timeout > 0 then + timer_task = self:_timer():post(function (cancel) + if not cancel and running:is_suspended() then + pollerdata.co_waiting = nil + running:waitobj_set(nil) + return self:co_resume(running, 0) + end + return true + end, timeout) + end + running:_timer_task_set(timer_task) + + -- set fwatcher status + pollerdata.object_event = 0 + pollerdata.object_pending = 0 + pollerdata.co_waiting = running + + -- save the waiting poller object + running:waitobj_set(obj) + + -- wait + local ok = self:co_suspend() + return ok, pollerdata.object_event end -- cancel poller object events @@ -794,7 +860,7 @@ function scheduler:poller_cancel(obj) -- reset the pollerdata data local pollerdata = self:_poller_data(obj) if pollerdata then - if pollerdata.poller_events_wait ~= 0 or obj:otype() == poller.OT_PROC then + if pollerdata.poller_events_wait ~= 0 or obj:otype() == poller.OT_PROC or obj:otype() == poller.OT_FWATCHER then local ok, errors = poller:remove(obj) if not ok then return false, errors @@ -818,7 +884,7 @@ function scheduler:stop() return true end --- run loop, schedule coroutine with socket/io and sub-processes +-- run loop, schedule coroutine with socket/io, sub-processes, fwatcher function scheduler:runloop() -- start loop diff --git a/xmake/core/sandbox/modules/import/core/base/fwatcher.lua b/xmake/core/sandbox/modules/import/core/base/fwatcher.lua new file mode 100644 index 000000000..9bf68442e --- /dev/null +++ b/xmake/core/sandbox/modules/import/core/base/fwatcher.lua @@ -0,0 +1,92 @@ +--!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 fwatcher.lua +-- + +-- define module +local sandbox_core_base_fwatcher = sandbox_core_base_fwatcher or {} + +-- load modules +local fwatcher = require("base/fwatcher") +local raise = require("sandbox/modules/raise") + +-- the fwatcher event type +sandbox_core_base_fwatcher.ET_MODIFY = fwatcher.ET_MODIFY +sandbox_core_base_fwatcher.ET_CREATE = fwatcher.ET_CREATE +sandbox_core_base_fwatcher.ET_DELETE = fwatcher.ET_DELETE + +-- add watch directory +function sandbox_core_base_fwatcher.add(watchdir, opt) + local ok, errors = fwatcher.add(watchdir, opt) + if not ok then + raise(errors) + end +end + +-- remove watch directory +function sandbox_core_base_fwatcher.remove(watchdir) + local ok, errors = fwatcher.remove(watchdir) + if not ok then + raise(errors) + end +end + +-- wait event +function sandbox_core_base_fwatcher.wait(timeout) + local ok, event_or_errors = fwatcher.wait(timeout) + if ok < 0 and event_or_errors then + raise(event_or_errors) + end + return ok, event_or_errors +end + +-- watch watchdirs +function sandbox_core_base_fwatcher.watchdirs(watchdirs, callback, opt) + local ok, errors = fwatcher.watchdirs(watchdirs, callback, opt) + if not ok then + raise(errors) + end +end + +-- watch created file path +function sandbox_core_base_fwatcher.on_created(watchdirs, callback, opt) + local ok, errors = fwatcher.on_created(watchdirs, callback, opt) + if not ok then + raise(errors) + end +end + +-- watch modified file path +function sandbox_core_base_fwatcher.on_modified(watchdirs, callback, opt) + local ok, errors = fwatcher.on_modified(watchdirs, callback, opt) + if not ok then + raise(errors) + end +end + +-- watch deleted file path +function sandbox_core_base_fwatcher.on_deleted(watchdirs, callback, opt) + local ok, errors = fwatcher.on_deleted(watchdirs, callback, opt) + if not ok then + raise(errors) + end +end + +-- return module +return sandbox_core_base_fwatcher + diff --git a/xmake/core/sandbox/modules/import/core/base/process.lua b/xmake/core/sandbox/modules/import/core/base/process.lua index 2f9141fc1..884ae9ebe 100644 --- a/xmake/core/sandbox/modules/import/core/base/process.lua +++ b/xmake/core/sandbox/modules/import/core/base/process.lua @@ -31,11 +31,11 @@ sandbox_core_base_process._subprocess = sandbox_core_base_process._subprocess or -- wait subprocess function sandbox_core_base_instance.wait(proc, timeout) - local ok, status, errors = proc:_wait(timeout) - if errors then - raise(errors) + local ok, status_or_errors = proc:_wait(timeout) + if ok < 0 and status_or_errors then + raise(status_or_errors) end - return ok, status + return ok, status_or_errors end -- kill subprocess diff --git a/xmake/plugins/watch/main.lua b/xmake/plugins/watch/main.lua new file mode 100644 index 000000000..eb5f3df55 --- /dev/null +++ b/xmake/plugins/watch/main.lua @@ -0,0 +1,123 @@ +--!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 main.lua +-- + +-- imports +import("core.base.option") +import("core.base.fwatcher") +import("core.project.config") + +-- add watchdir +function _add_watchdir(watchdir, opt) + opt = opt or {} + cprint("watching ${bright}%s/%s${clear} ..", watchdir, opt.recursion and "**" or "*") + fwatcher.add(watchdir, opt) +end + +-- add watchdirs +function _add_watchdirs() + local watchdirs = option.get("watchdirs") + if watchdirs then + for _, watchdir in ipairs(path.splitenv(watchdirs)) do + for _, dir in ipairs(os.dirs(watchdir)) do + _add_watchdir(dir, {recursion = true}) + end + end + elseif os.isfile(os.projectfile()) then + watchdirs = os.dirs(path.join(os.projectdir(), "*|.*")) + for _, watchdir in ipairs(watchdirs) do + local buildir = path.absolute(config.buildir()) + if path.absolute(watchdir) ~= buildir then + _add_watchdir(watchdir, {recursion = true}) + end + end + _add_watchdir(os.projectdir()) + else + _add_watchdir(watchdir, {recursion = true}) + end +end + +-- run command +function _run_command() + try + { + function () + local command = option.get("command") + local scriptfile = option.get("script") + if command then + for _, command in ipairs(command:split(";")) do + os.exec(command) + end + elseif scriptfile and os.isfile(scriptfile) and path.extension(scriptfile) == ".lua" then + local script = import(path.basename(scriptfile), + {rootdir = path.directory(scriptfile), anonymous = true}) + script() + elseif os.isfile(os.projectfile()) then + local argv = {"build"} + if option.get("verbose") then + table.insert(argv, "-v") + end + if option.get("diagnosis") then + table.insert(argv, "-D") + end + if option.get("warning") then + table.insert(argv, "-w") + end + local target = option.get("target") + if target then + table.insert(argv, target) + end + os.execv(os.programfile(), argv) + end + end, + catch + { + function (errors) + cprint(tostring(errors)) + end + } + } +end + +function main() + + -- add watchdirs + _add_watchdirs() + + -- do watch + local count = 0 + while true do + local ok, event = fwatcher.wait(300) + if ok > 0 then + local status + if event.type == fwatcher.ET_CREATE then + status = "created" + elseif event.type == fwatcher.ET_MODIFY then + status = "modified" + elseif event.type == fwatcher.ET_DELETE then + status = "deleted" + end + print(event.path, status) + count = count + 1 + elseif count > 0 then + _run_command() + count = 0 + end + end +end diff --git a/xmake/plugins/watch/xmake.lua b/xmake/plugins/watch/xmake.lua new file mode 100644 index 000000000..c35db328c --- /dev/null +++ b/xmake/plugins/watch/xmake.lua @@ -0,0 +1,46 @@ +--!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 xmake.lua +-- + +task("watch") + set_category("plugin") + on_run("main") + set_menu { + usage = "xmake watch [options] [arguments]" + , description = "Watch the project directories and run command." + , options = + { + {'c', "command" , "kv" , nil , "Run the given command instead of the default build command.", + "e.g.", + " $ xmake watch -c 'xmake -rv'", + " $ xmake watch -c 'xmake -vD; xmake run hello'"}, + {'s', "script" , "kv" , nil , "Run the given lua script file.", + "e.g.", + " $ xmake watch -s /tmp/watch.lua"}, + {'d', "watchdirs" , "kv" , nil , "Set the given watch directories, the project directories will be watched by default.", + "e.g.", + " $ xmake watch -d src", + " $ xmake watch -d 'src/*" .. path.envsep() .. "tests/**/subdir'"}, + {'t', "target" , "v" , nil , "Build the given target.", + values = function (complete, opt) return import("private.utils.complete_helper.targets")(complete, opt) end } + } + } + + + |
