From a1c66b75e90a25cab99f865c539df5451abd9edf Mon Sep 17 00:00:00 2001 From: Zijian Zhang Date: Mon, 22 Jan 2024 20:50:48 +0800 Subject: [PATCH] build: trying to fix libnode --- .github/workflows/build.yml | 4 +- src/main/Loader.cpp | 26 +- src/main/NodeJsHelper.cpp | 995 ++++++++++++++++++------------------ xmake.lua | 38 +- 4 files changed, 534 insertions(+), 529 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 415c972..341d75f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,9 +8,9 @@ jobs: strategy: matrix: backend: - - libnode + # - libnode - lua - - python310 + # - python310 - quickjs runs-on: windows-latest steps: diff --git a/src/main/Loader.cpp b/src/main/Loader.cpp index aac9d93..353d4c7 100644 --- a/src/main/Loader.cpp +++ b/src/main/Loader.cpp @@ -114,7 +114,7 @@ void LoadMain() { #ifdef LLSE_BACKEND_NODEJS // NodeJs后端 - 主加载 void LoadMain_NodeJs() { - logger.info(tr("llse.loader.loadMain.start", fmt::arg("type", "Node.js"))); + // logger.info(tr("llse.loader.loadMain.start", fmt::arg("type", "Node.js"))); int installCount = 0; int count = 0; @@ -131,7 +131,7 @@ void LoadMain_NodeJs() { } } catch (...) {} } else { - logger.warn(tr("llse.loader.loadMain.nodejs.ignored", fmt::arg("path", targetDirStr))); + // logger.warn(tr("llse.loader.loadMain.nodejs.ignored", fmt::arg("path", targetDirStr))); } } } @@ -143,28 +143,28 @@ void LoadMain_NodeJs() { std::filesystem::path pth = i.path(); std::string packFilePathStr = ll::string_utils::u8str2str(pth.make_preferred().u8string()); if (i.is_regular_file() && packFilePathStr.ends_with(LLSE_PLUGIN_PACKAGE_EXTENSION)) { - logger.info(tr("llse.loader.loadMain.nodejs.installPack.start", fmt::arg("path", packFilePathStr))); + // logger.info(tr("llse.loader.loadMain.nodejs.installPack.start", fmt::arg("path", packFilePathStr))); try { if (!PluginManager::loadPlugin(packFilePathStr, false, true)) { - logger.error(tr("llse.loader.loadMain.nodejs.installPack.fail", packFilePathStr)); + // logger.error(tr("llse.loader.loadMain.nodejs.installPack.fail", packFilePathStr)); } ++count; ++installCount; } catch (...) { // not matched backend type - logger.warn(tr("llse.loader.loadMain.nodejs.ignored", fmt::arg("path", packFilePathStr))); + // logger.warn(tr("llse.loader.loadMain.nodejs.ignored", fmt::arg("path", packFilePathStr))); } } } - logger.info(tr("llse.loader.loadMain.done", fmt::arg("count", count), fmt::arg("type", "Node.js"))); + // logger.info(tr("llse.loader.loadMain.done", fmt::arg("count", count), fmt::arg("type", "Node.js"))); } #endif #ifdef LLSE_BACKEND_PYTHON // Python后端 - 主加载 void LoadMain_Python() { - logger.info(tr("llse.loader.loadMain.start", fmt::arg("type", "Python"))); + // logger.info(tr("llse.loader.loadMain.start", fmt::arg("type", "Python"))); int installCount = 0; int count = 0; @@ -181,7 +181,7 @@ void LoadMain_Python() { } } catch (...) {} } else { - logger.warn(tr("llse.loader.loadMain.python.ignored", fmt::arg("path", targetDirStr))); + // logger.warn(tr("llse.loader.loadMain.python.ignored", fmt::arg("path", targetDirStr))); } } } @@ -193,16 +193,16 @@ void LoadMain_Python() { std::filesystem::path pth = i.path(); std::string packFilePathStr = ll::string_utils::u8str2str(pth.make_preferred().u8string()); if (i.is_regular_file() && packFilePathStr.ends_with(LLSE_PLUGIN_PACKAGE_EXTENSION)) { - logger.info(tr("llse.loader.loadMain.python.installPack.start", fmt::arg("path", packFilePathStr))); + // logger.info(tr("llse.loader.loadMain.python.installPack.start", fmt::arg("path", packFilePathStr))); try { if (!PluginManager::loadPlugin(packFilePathStr, false, true)) { - logger.error(tr("llse.loader.loadMain.python.installPack.fail", packFilePathStr)); + // logger.error(tr("llse.loader.loadMain.python.installPack.fail", packFilePathStr)); } ++count; ++installCount; } catch (...) { // not matched backend type - logger.warn(tr("llse.loader.loadMain.python.ignored", packFilePathStr)); + // logger.warn(tr("llse.loader.loadMain.python.ignored", packFilePathStr)); } } } @@ -218,7 +218,7 @@ void LoadMain_Python() { } } - logger.info(tr("llse.loader.loadMain.done", fmt::arg("count", count), fmt::arg("type", "Python"))); + // logger.info(tr("llse.loader.loadMain.done", fmt::arg("count", count), fmt::arg("type", "Python"))); } #endif @@ -232,4 +232,4 @@ void LoadMain_Package() { LoadMain_Python(); return; #endif -} \ No newline at end of file +} diff --git a/src/main/NodeJsHelper.cpp b/src/main/NodeJsHelper.cpp index 27a90f5..868e159 100644 --- a/src/main/NodeJsHelper.cpp +++ b/src/main/NodeJsHelper.cpp @@ -1,497 +1,498 @@ -#pragma warning(disable : 4251) -#include "main/Configs.h" -#if defined(LLSE_BACKEND_NODEJS) -#include "api/CommandAPI.h" -#include "api/CommandCompatibleAPI.h" -#include "api/EventAPI.h" -#include "engine/EngineManager.h" -#include "engine/EngineOwnData.h" -#include "engine/RemoteCall.h" -#include "main/Global.h" -#include "main/NodeJsHelper.h" - -#include -#include -#include -#include -#include - -// pre-declare -extern void BindAPIs(ScriptEngine* engine); - -namespace NodeJsHelper { - -bool nodeJsInited = false; -std::vector args; -std::vector exec_args; - -std::unique_ptr platform = nullptr; -std::unordered_map environments; -std::unordered_map>* setups = - new std::unordered_map>(); -std::unordered_map isRunning; -std::unordered_map uvLoopTask; - -bool initNodeJs() { - // Init NodeJs - WCHAR buf[MAX_PATH]; - GetCurrentDirectory(MAX_PATH, buf); - auto path = wstr2str(buf) + "\\bedrock_server_mod.exe"; - char* cPath = (char*)path.c_str(); - uv_setup_args(1, &cPath); - args = {path}; - std::vector errors; - auto exitCode = node::InitializeNodeWithArgs(&args, &exec_args, &errors); - if (exitCode != 0) { - logger.error("Failed to initialize node! NodeJs plugins won't be loaded"); - return false; - } - - // Init V8 - using namespace v8; - platform = node::MultiIsolatePlatform::Create(std::thread::hardware_concurrency()); - V8::InitializePlatform(platform.get()); - V8::Initialize(); - - nodeJsInited = true; - return true; -} - -void shutdownNodeJs() { - v8::V8::Dispose(); - v8::V8::ShutdownPlatform(); -} - -script::ScriptEngine* newEngine() { - if (!nodeJsInited && !initNodeJs()) { - return nullptr; - } - std::vector errors; - std::unique_ptr setup = node::CommonEnvironmentSetup::Create( - platform.get(), - &errors, - args, - exec_args, - node::EnvironmentFlags::kOwnsProcessState - ); - // if kOwnsInspector set, inspector_agent.cc:681 - // CHECK_EQ(start_io_thread_async_initialized.exchange(true), false) fail! - - if (!setup) { - for (const std::string& err : errors) logger.error("CommonEnvironmentSetup Error: {}", err.c_str()); - return nullptr; - } - v8::Isolate* isolate = setup->isolate(); - node::Environment* env = setup->env(); - - v8::Locker locker(isolate); - v8::Isolate::Scope isolate_scope(isolate); - v8::HandleScope handle_scope(isolate); - v8::Context::Scope context_scope(setup->context()); - - script::ScriptEngine* engine = new script::ScriptEngineImpl({}, isolate, setup->context(), false); - - logger.debug("Initialize ScriptEngine for node.js [{}]", (void*)engine); - environments[engine] = env; - (*setups)[engine] = std::move(setup); - isRunning[env] = true; - - node::AddEnvironmentCleanupHook( - isolate, - [](void* arg) { - static_cast(arg)->destroy(); - logger.debug("Destory ScriptEngine for node.js [{}]", arg); - logger.debug("Destroy EnvironmentCleanupHook"); - }, - engine - ); - return engine; -} - -bool loadPluginCode(script::ScriptEngine* engine, std::string entryScriptPath, std::string pluginDirPath) { - auto mainScripts = ReadAllFile(entryScriptPath); - if (!mainScripts) { - return false; - } - - // Process requireDir - if (!pluginDirPath.ends_with('/')) pluginDirPath += "/"; - pluginDirPath = ReplaceStr(pluginDirPath, "\\", "/"); - entryScriptPath = ReplaceStr(entryScriptPath, "\\", "/"); - - // Find setup - auto it = setups->find(engine); - if (it == setups->end()) return false; - - auto isolate = it->second->isolate(); - auto env = it->second->env(); - - try { - using namespace v8; - EngineScope enter(engine); - - string executeJs = "const __LLSE_PublicRequire = " - "require('module').createRequire(process.cwd() + '/" - + pluginDirPath + "');" - + "const __LLSE_PublicModule = require('module'); " - "__LLSE_PublicModule.exports = {};" - + "ll.export = ll.exports; ll.import = ll.imports; " - - + "(function (exports, require, module, __filename, __dirname) { " + *mainScripts - + "\n})({}, __LLSE_PublicRequire, __LLSE_PublicModule, '" + entryScriptPath + "', '" - + pluginDirPath + "'); "; // TODO __filename & __dirname need to be reviewed - // TODO: ESM Support - - // Set exit handler - node::SetProcessExitHandler(env, [](node::Environment* env_, int exit_code) { stopEngine(getEngine(env_)); }); - - // Load code - MaybeLocal loadenv_ret = node::LoadEnvironment(env, executeJs.c_str()); - if (loadenv_ret.IsEmpty()) // There has been a JS exception. - { - node::Stop(env); - uv_stop(it->second->event_loop()); - return false; - } - - // Start libuv event loop - uvLoopTask[env] = Schedule::repeat( - [engine, env, isRunningMap{&isRunning}, eventLoop{it->second->event_loop()}]() { - if (!(ll::getServerStatus() != ll::ServerStatus::Running) && (*isRunningMap)[env]) { - EngineScope enter(engine); - uv_run(eventLoop, UV_RUN_NOWAIT); - } - if ((ll::getServerStatus() != ll::ServerStatus::Running)) { - uv_stop(eventLoop); - logger.debug("Destroy ServerStopping"); - } - }, - 2 - ); - - return true; - } catch (...) { - return false; - } -} - -node::Environment* getEnvironmentOf(script::ScriptEngine* engine) { - auto it = environments.find(engine); - if (it == environments.end()) return nullptr; - return it->second; -} - -v8::Isolate* getIsolateOf(script::ScriptEngine* engine) { - auto it = setups->find(engine); - if (it == setups->end()) return nullptr; - return it->second->isolate(); -} - -bool stopEngine(node::Environment* env) { - if (!env) return false; - try { - // Set flag - isRunning[env] = false; - - // Stop code executing - node::Stop(env); - - // Stop libuv event loop - auto it = uvLoopTask.find(env); - if (it != uvLoopTask.end()) { - it->second.cancel(); - } - - return true; - } catch (...) { - logger.error("Fail to stop engine {}", (void*)env); - return false; - } -} - -bool stopEngine(script::ScriptEngine* engine) { - logger.info("NodeJs plugin {} exited.", ENGINE_GET_DATA(engine)->pluginName); - auto env = NodeJsHelper::getEnvironmentOf(engine); - return stopEngine(env); -} - -script::ScriptEngine* getEngine(node::Environment* env) { - for (auto& [engine, environment] : environments) - if (env == environment) return engine; - return nullptr; -} - -// Load NodeJs plugin -// This function must be called in correct backend -bool loadNodeJsPlugin(std::string dirPath, const std::string& packagePath, bool isHotLoad) { - // "dirPath" is always public temp dir (LLSE_PLUGIN_PACKAGE_TEMP_DIR) - if (dirPath == LLSE_PLUGIN_PACKAGE_TEMP_DIR) { - // Need to copy from temp dir to installed dir - if (std::filesystem::exists(LLSE_PLUGIN_PACKAGE_TEMP_DIR "/package.json")) { - auto pluginName = NodeJsHelper::getPluginPackageName(LLSE_PLUGIN_PACKAGE_TEMP_DIR); - if (pluginName.empty()) { - pluginName = UTF82String(filesystem::path(packagePath).filename().replace_extension("").u8string()); - } - auto dest = std::filesystem::path(LLSE_PLUGINS_ROOT_DIR).append(pluginName); - - // copy files - std::error_code ec; - // if (filesystem::exists(dest)) - // filesystem::remove_all(dest, ec); - std::filesystem::copy( - LLSE_PLUGIN_PACKAGE_TEMP_DIR "/", - dest, - filesystem::copy_options::overwrite_existing | filesystem::copy_options::recursive, - ec - ); - - // reset dirPath - dirPath = UTF82String(dest.u8string()); - } - // remove temp dir - std::error_code ec; - std::filesystem::remove_all(LLSE_PLUGIN_PACKAGE_TEMP_DIR, ec); - } - - std::string entryPath = NodeJsHelper::findEntryScript(dirPath); - if (entryPath.empty()) return false; - std::string pluginName = NodeJsHelper::getPluginPackageName(dirPath); - - // Run "npm install" if needed - if (NodeJsHelper::doesPluginPackHasDependency(dirPath) - && !filesystem::exists(filesystem::path(dirPath) / "node_modules")) { - int exitCode = 0; - logger.info( - tr("llse.loader.nodejs.executeNpmInstall.start", - fmt::arg("name", UTF82String(filesystem::path(dirPath).filename().u8string()))) - ); - if ((exitCode = NodeJsHelper::executeNpmCommand("npm install", dirPath)) == 0) - logger.info(tr("llse.loader.nodejs.executeNpmInstall.success")); - else logger.error(tr("llse.loader.nodejs.executeNpmInstall.fail", fmt::arg("code", exitCode))); - } - - // Create engine & Load plugin - ScriptEngine* engine = nullptr; - node::Environment* env = nullptr; - try { - engine = EngineManager::newEngine(); - { - EngineScope enter(engine); - // setData - ENGINE_OWN_DATA()->pluginName = pluginName; - ENGINE_OWN_DATA()->pluginFileOrDirPath = dirPath; - ENGINE_OWN_DATA()->logger.title = pluginName; - // bindAPIs - BindAPIs(engine); - } - if (!NodeJsHelper::loadPluginCode(engine, entryPath, dirPath)) throw "Uncaught exception thrown in code"; - - if (!PluginManager::getPlugin(pluginName)) { - // Plugin did't register itself. Help to register it - string description = pluginName; - ll::Version ver(1, 0, 0); - std::map others = {}; - - // Read information from package.json - try { - std::filesystem::path packageFilePath = std::filesystem::path(dirPath) / "package.json"; - std::ifstream file(UTF82String(packageFilePath.make_preferred().u8string())); - nlohmann::json j; - file >> j; - file.close(); - - // description - if (j.contains("description")) { - description = j["description"].get(); - } - // version - if (j.contains("version") && j["version"].is_string()) { - ver = ll::Version::parse(j["version"].get()); - } - // license - if (j.contains("license") && j["license"].is_string()) { - others["License"] = j["license"].get(); - } else if (j["license"].is_object() && j["license"].contains("url")) { - others["License"] = j["license"]["url"].get(); - } - // repository - if (j.contains("repository") && j["repository"].is_string()) { - others["Repository"] = j["repository"].get(); - } else if (j["repository"].is_object() && j["repository"].contains("url")) { - others["Repository"] = j["repository"]["url"].get(); - } - } catch (...) { - logger.warn(tr("llse.loader.nodejs.register.fail", fmt::arg("name", pluginName))); - } - - // register - PluginManager::registerPlugin(dirPath, pluginName, description, ver, others); - } - - // Call necessary events when at hot load - if (isHotLoad) LLSECallEventsOnHotLoad(engine); - - // Success - logger.info(tr("llse.loader.loadMain.loadedPlugin", fmt::arg("type", "Node.js"), fmt::arg("name", pluginName))); - return true; - } catch (const Exception& e) { - logger.error("Fail to load " + dirPath + "!"); - if (engine) { - EngineScope enter(engine); - logger.error("In Plugin: " + ENGINE_OWN_DATA()->pluginName); - PrintException(e); - ExitEngineScope exit; - - // NodeJs use his own setTimeout, so no need to remove - // LLSERemoveTimeTaskData(engine); - - LLSERemoveAllEventListeners(engine); - LLSERemoveCmdRegister(engine); - LLSERemoveCmdCallback(engine); - LLSERemoveAllExportedFuncs(engine); - - engine->getData().reset(); - EngineManager::unRegisterEngine(engine); - } - if (engine) { - NodeJsHelper::stopEngine(engine); - } - } catch (const std::exception& e) { - logger.error("Fail to load " + dirPath + "!"); - logger.error(ll::string_utils::tou8str(e.what())); - } catch (...) { - logger.error("Fail to load " + dirPath + "!"); - } - return false; -} - -std::string findEntryScript(const std::string& dirPath) { - auto dirPath_obj = std::filesystem::path(dirPath); - - std::filesystem::path packageFilePath = dirPath_obj / "package.json"; - if (!std::filesystem::exists(packageFilePath)) return ""; - - try { - std::ifstream file(UTF82String(packageFilePath.make_preferred().u8string())); - nlohmann::json j; - file >> j; - std::string entryFile = "index.js"; - if (j.contains("main")) { - entryFile = j["main"].get(); - } - auto entryPath = std::filesystem::canonical(dirPath_obj / std::filesystem::path(entryFile)); - if (!std::filesystem::exists(entryPath)) return ""; - else return UTF82String(entryPath.u8string()); - } catch (...) { - return ""; - } -} - -std::string getPluginPackageName(const std::string& dirPath) { - auto dirPath_obj = std::filesystem::path(dirPath); - - std::filesystem::path packageFilePath = dirPath_obj / std::filesystem::path("package.json"); - if (!std::filesystem::exists(packageFilePath)) return ""; - - try { - std::ifstream file(UTF82String(packageFilePath.make_preferred().u8string())); - nlohmann::json j; - file >> j; - std::string packageName = ""; - if (j.contains("name")) { - packageName = j["name"].get(); - } - return packageName; - } catch (...) { - return ""; - } -} - -bool doesPluginPackHasDependency(const std::string& dirPath) { - auto dirPath_obj = std::filesystem::path(dirPath); - - std::filesystem::path packageFilePath = dirPath_obj / std::filesystem::path("package.json"); - if (!std::filesystem::exists(packageFilePath)) return false; - - try { - std::ifstream file(UTF82String(packageFilePath.make_preferred().u8string())); - nlohmann::json j; - file >> j; - if (j.contains("dependencies")) { - return true; - } - return false; - } catch (...) { - return false; - } -} - -bool processConsoleNpmCmd(const std::string& cmd) { -#ifdef LLSE_BACKEND_NODEJS - if (StartsWith(cmd, "npm ")) { - executeNpmCommand(cmd); - return false; - } else return true; -#else - return true; -#endif -} - -int executeNpmCommand(std::string cmd, std::string workingDir) { - if (!nodeJsInited && !initNodeJs()) { - return -1; - } - std::vector errors; - std::unique_ptr setup = node::CommonEnvironmentSetup::Create( - platform.get(), - &errors, - args, - exec_args, - node::EnvironmentFlags::kOwnsProcessState - ); - // if kOwnsInspector set, inspector_agent.cc:681 - // CHECK_EQ(start_io_thread_async_initialized.exchange(true), false) fail! - - if (!setup) { - for (const std::string& err : errors) logger.error("CommonEnvironmentSetup Error: {}", err.c_str()); - return -1; - } - v8::Isolate* isolate = setup->isolate(); - node::Environment* env = setup->env(); - int exit_code = 0; - - // Process workingDir - workingDir = ReplaceStr(workingDir, "\\", "/"); - - { - using namespace v8; - v8::Locker locker(isolate); - v8::Isolate::Scope isolate_scope(isolate); - v8::HandleScope handle_scope(isolate); - v8::Context::Scope context_scope(setup->context()); - - string executeJs = "const oldCwd = process.cwd();" - "const publicRequire = require('module').createRequire(oldCwd + " - "'/plugins/lib/');" - "require('process').chdir('" - + workingDir + "');" + "publicRequire('npm-js-interface')('" + cmd + "');" - + "require('process').chdir(oldCwd);"; - - try { - node::SetProcessExitHandler(env, [&](node::Environment* env_, int exit_code) { node::Stop(env); }); - MaybeLocal loadenv_ret = node::LoadEnvironment(env, executeJs.c_str()); - if (loadenv_ret.IsEmpty()) // There has been a JS exception. - throw "error"; - exit_code = node::SpinEventLoop(env).FromMaybe(1); - } catch (...) { - logger.error("Fail to execute NPM command. Error occurs"); - } - } - - node::Stop(env); - return exit_code; -} - -} // namespace NodeJsHelper - -#endif +// #pragma warning(disable : 4251) +// #include "main/Configs.h" +// #if defined(LLSE_BACKEND_NODEJS) +// #include "api/CommandAPI.h" +// #include "api/CommandCompatibleAPI.h" +// #include "api/EventAPI.h" +// #include "engine/EngineManager.h" +// #include "engine/EngineOwnData.h" +// #include "engine/RemoteCall.h" +// #include "main/Global.h" +// #include "main/NodeJsHelper.h" + +// #include +// #include +// #include +// #include +// #include + +// // pre-declare +// extern void BindAPIs(ScriptEngine* engine); + +// namespace NodeJsHelper { + +// bool nodeJsInited = false; +// std::vector args; +// std::vector exec_args; + +// std::unique_ptr platform = nullptr; +// std::unordered_map environments; +// std::unordered_map>* setups = +// new std::unordered_map>(); +// std::unordered_map isRunning; +// std::unordered_map uvLoopTask; + +// bool initNodeJs() { +// // Init NodeJs +// WCHAR buf[MAX_PATH]; +// GetCurrentDirectory(MAX_PATH, buf); +// auto path = wstr2str(buf) + "\\bedrock_server_mod.exe"; +// char* cPath = (char*)path.c_str(); +// uv_setup_args(1, &cPath); +// args = {path}; +// std::vector errors; +// auto exitCode = node::InitializeNodeWithArgs(&args, &exec_args, &errors); +// if (exitCode != 0) { +// logger.error("Failed to initialize node! NodeJs plugins won't be loaded"); +// return false; +// } + +// // Init V8 +// using namespace v8; +// platform = node::MultiIsolatePlatform::Create(std::thread::hardware_concurrency()); +// V8::InitializePlatform(platform.get()); +// V8::Initialize(); + +// nodeJsInited = true; +// return true; +// } + +// void shutdownNodeJs() { +// v8::V8::Dispose(); +// v8::V8::ShutdownPlatform(); +// } + +// script::ScriptEngine* newEngine() { +// if (!nodeJsInited && !initNodeJs()) { +// return nullptr; +// } +// std::vector errors; +// std::unique_ptr setup = node::CommonEnvironmentSetup::Create( +// platform.get(), +// &errors, +// args, +// exec_args, +// node::EnvironmentFlags::kOwnsProcessState +// ); +// // if kOwnsInspector set, inspector_agent.cc:681 +// // CHECK_EQ(start_io_thread_async_initialized.exchange(true), false) fail! + +// if (!setup) { +// for (const std::string& err : errors) logger.error("CommonEnvironmentSetup Error: {}", err.c_str()); +// return nullptr; +// } +// v8::Isolate* isolate = setup->isolate(); +// node::Environment* env = setup->env(); + +// v8::Locker locker(isolate); +// v8::Isolate::Scope isolate_scope(isolate); +// v8::HandleScope handle_scope(isolate); +// v8::Context::Scope context_scope(setup->context()); + +// script::ScriptEngine* engine = new script::ScriptEngineImpl({}, isolate, setup->context(), false); + +// logger.debug("Initialize ScriptEngine for node.js [{}]", (void*)engine); +// environments[engine] = env; +// (*setups)[engine] = std::move(setup); +// isRunning[env] = true; + +// node::AddEnvironmentCleanupHook( +// isolate, +// [](void* arg) { +// static_cast(arg)->destroy(); +// logger.debug("Destory ScriptEngine for node.js [{}]", arg); +// logger.debug("Destroy EnvironmentCleanupHook"); +// }, +// engine +// ); +// return engine; +// } + +// bool loadPluginCode(script::ScriptEngine* engine, std::string entryScriptPath, std::string pluginDirPath) { +// auto mainScripts = ReadAllFile(entryScriptPath); +// if (!mainScripts) { +// return false; +// } + +// // Process requireDir +// if (!pluginDirPath.ends_with('/')) pluginDirPath += "/"; +// pluginDirPath = ReplaceStr(pluginDirPath, "\\", "/"); +// entryScriptPath = ReplaceStr(entryScriptPath, "\\", "/"); + +// // Find setup +// auto it = setups->find(engine); +// if (it == setups->end()) return false; + +// auto isolate = it->second->isolate(); +// auto env = it->second->env(); + +// try { +// using namespace v8; +// EngineScope enter(engine); + +// string executeJs = "const __LLSE_PublicRequire = " +// "require('module').createRequire(process.cwd() + '/" +// + pluginDirPath + "');" +// + "const __LLSE_PublicModule = require('module'); " +// "__LLSE_PublicModule.exports = {};" +// + "ll.export = ll.exports; ll.import = ll.imports; " + +// + "(function (exports, require, module, __filename, __dirname) { " + *mainScripts +// + "\n})({}, __LLSE_PublicRequire, __LLSE_PublicModule, '" + entryScriptPath + "', '" +// + pluginDirPath + "'); "; // TODO __filename & __dirname need to be reviewed +// // TODO: ESM Support + +// // Set exit handler +// node::SetProcessExitHandler(env, [](node::Environment* env_, int exit_code) { stopEngine(getEngine(env_)); +// }); + +// // Load code +// MaybeLocal loadenv_ret = node::LoadEnvironment(env, executeJs.c_str()); +// if (loadenv_ret.IsEmpty()) // There has been a JS exception. +// { +// node::Stop(env); +// uv_stop(it->second->event_loop()); +// return false; +// } + +// // Start libuv event loop +// uvLoopTask[env] = Schedule::repeat( +// [engine, env, isRunningMap{&isRunning}, eventLoop{it->second->event_loop()}]() { +// if (!(ll::getServerStatus() != ll::ServerStatus::Running) && (*isRunningMap)[env]) { +// EngineScope enter(engine); +// uv_run(eventLoop, UV_RUN_NOWAIT); +// } +// if ((ll::getServerStatus() != ll::ServerStatus::Running)) { +// uv_stop(eventLoop); +// logger.debug("Destroy ServerStopping"); +// } +// }, +// 2 +// ); + +// return true; +// } catch (...) { +// return false; +// } +// } + +// node::Environment* getEnvironmentOf(script::ScriptEngine* engine) { +// auto it = environments.find(engine); +// if (it == environments.end()) return nullptr; +// return it->second; +// } + +// v8::Isolate* getIsolateOf(script::ScriptEngine* engine) { +// auto it = setups->find(engine); +// if (it == setups->end()) return nullptr; +// return it->second->isolate(); +// } + +// bool stopEngine(node::Environment* env) { +// if (!env) return false; +// try { +// // Set flag +// isRunning[env] = false; + +// // Stop code executing +// node::Stop(env); + +// // Stop libuv event loop +// auto it = uvLoopTask.find(env); +// if (it != uvLoopTask.end()) { +// it->second.cancel(); +// } + +// return true; +// } catch (...) { +// logger.error("Fail to stop engine {}", (void*)env); +// return false; +// } +// } + +// bool stopEngine(script::ScriptEngine* engine) { +// logger.info("NodeJs plugin {} exited.", ENGINE_GET_DATA(engine)->pluginName); +// auto env = NodeJsHelper::getEnvironmentOf(engine); +// return stopEngine(env); +// } + +// script::ScriptEngine* getEngine(node::Environment* env) { +// for (auto& [engine, environment] : environments) +// if (env == environment) return engine; +// return nullptr; +// } + +// // Load NodeJs plugin +// // This function must be called in correct backend +// bool loadNodeJsPlugin(std::string dirPath, const std::string& packagePath, bool isHotLoad) { +// // "dirPath" is always public temp dir (LLSE_PLUGIN_PACKAGE_TEMP_DIR) +// if (dirPath == LLSE_PLUGIN_PACKAGE_TEMP_DIR) { +// // Need to copy from temp dir to installed dir +// if (std::filesystem::exists(LLSE_PLUGIN_PACKAGE_TEMP_DIR "/package.json")) { +// auto pluginName = NodeJsHelper::getPluginPackageName(LLSE_PLUGIN_PACKAGE_TEMP_DIR); +// if (pluginName.empty()) { +// pluginName = UTF82String(filesystem::path(packagePath).filename().replace_extension("").u8string()); +// } +// auto dest = std::filesystem::path(LLSE_PLUGINS_ROOT_DIR).append(pluginName); + +// // copy files +// std::error_code ec; +// // if (filesystem::exists(dest)) +// // filesystem::remove_all(dest, ec); +// std::filesystem::copy( +// LLSE_PLUGIN_PACKAGE_TEMP_DIR "/", +// dest, +// filesystem::copy_options::overwrite_existing | filesystem::copy_options::recursive, +// ec +// ); + +// // reset dirPath +// dirPath = UTF82String(dest.u8string()); +// } +// // remove temp dir +// std::error_code ec; +// std::filesystem::remove_all(LLSE_PLUGIN_PACKAGE_TEMP_DIR, ec); +// } + +// std::string entryPath = NodeJsHelper::findEntryScript(dirPath); +// if (entryPath.empty()) return false; +// std::string pluginName = NodeJsHelper::getPluginPackageName(dirPath); + +// // Run "npm install" if needed +// if (NodeJsHelper::doesPluginPackHasDependency(dirPath) +// && !filesystem::exists(filesystem::path(dirPath) / "node_modules")) { +// int exitCode = 0; +// logger.info( +// tr("llse.loader.nodejs.executeNpmInstall.start", +// fmt::arg("name", UTF82String(filesystem::path(dirPath).filename().u8string()))) +// ); +// if ((exitCode = NodeJsHelper::executeNpmCommand("npm install", dirPath)) == 0) +// logger.info(tr("llse.loader.nodejs.executeNpmInstall.success")); +// else logger.error(tr("llse.loader.nodejs.executeNpmInstall.fail", fmt::arg("code", exitCode))); +// } + +// // Create engine & Load plugin +// ScriptEngine* engine = nullptr; +// node::Environment* env = nullptr; +// try { +// engine = EngineManager::newEngine(); +// { +// EngineScope enter(engine); +// // setData +// ENGINE_OWN_DATA()->pluginName = pluginName; +// ENGINE_OWN_DATA()->pluginFileOrDirPath = dirPath; +// ENGINE_OWN_DATA()->logger.title = pluginName; +// // bindAPIs +// BindAPIs(engine); +// } +// if (!NodeJsHelper::loadPluginCode(engine, entryPath, dirPath)) throw "Uncaught exception thrown in code"; + +// if (!PluginManager::getPlugin(pluginName)) { +// // Plugin did't register itself. Help to register it +// string description = pluginName; +// ll::Version ver(1, 0, 0); +// std::map others = {}; + +// // Read information from package.json +// try { +// std::filesystem::path packageFilePath = std::filesystem::path(dirPath) / "package.json"; +// std::ifstream file(UTF82String(packageFilePath.make_preferred().u8string())); +// nlohmann::json j; +// file >> j; +// file.close(); + +// // description +// if (j.contains("description")) { +// description = j["description"].get(); +// } +// // version +// if (j.contains("version") && j["version"].is_string()) { +// ver = ll::Version::parse(j["version"].get()); +// } +// // license +// if (j.contains("license") && j["license"].is_string()) { +// others["License"] = j["license"].get(); +// } else if (j["license"].is_object() && j["license"].contains("url")) { +// others["License"] = j["license"]["url"].get(); +// } +// // repository +// if (j.contains("repository") && j["repository"].is_string()) { +// others["Repository"] = j["repository"].get(); +// } else if (j["repository"].is_object() && j["repository"].contains("url")) { +// others["Repository"] = j["repository"]["url"].get(); +// } +// } catch (...) { +// logger.warn(tr("llse.loader.nodejs.register.fail", fmt::arg("name", pluginName))); +// } + +// // register +// PluginManager::registerPlugin(dirPath, pluginName, description, ver, others); +// } + +// // Call necessary events when at hot load +// if (isHotLoad) LLSECallEventsOnHotLoad(engine); + +// // Success +// logger.info(tr("llse.loader.loadMain.loadedPlugin", fmt::arg("type", "Node.js"), fmt::arg("name", +// pluginName))); return true; +// } catch (const Exception& e) { +// logger.error("Fail to load " + dirPath + "!"); +// if (engine) { +// EngineScope enter(engine); +// logger.error("In Plugin: " + ENGINE_OWN_DATA()->pluginName); +// PrintException(e); +// ExitEngineScope exit; + +// // NodeJs use his own setTimeout, so no need to remove +// // LLSERemoveTimeTaskData(engine); + +// LLSERemoveAllEventListeners(engine); +// LLSERemoveCmdRegister(engine); +// LLSERemoveCmdCallback(engine); +// LLSERemoveAllExportedFuncs(engine); + +// engine->getData().reset(); +// EngineManager::unRegisterEngine(engine); +// } +// if (engine) { +// NodeJsHelper::stopEngine(engine); +// } +// } catch (const std::exception& e) { +// logger.error("Fail to load " + dirPath + "!"); +// logger.error(ll::string_utils::tou8str(e.what())); +// } catch (...) { +// logger.error("Fail to load " + dirPath + "!"); +// } +// return false; +// } + +// std::string findEntryScript(const std::string& dirPath) { +// auto dirPath_obj = std::filesystem::path(dirPath); + +// std::filesystem::path packageFilePath = dirPath_obj / "package.json"; +// if (!std::filesystem::exists(packageFilePath)) return ""; + +// try { +// std::ifstream file(UTF82String(packageFilePath.make_preferred().u8string())); +// nlohmann::json j; +// file >> j; +// std::string entryFile = "index.js"; +// if (j.contains("main")) { +// entryFile = j["main"].get(); +// } +// auto entryPath = std::filesystem::canonical(dirPath_obj / std::filesystem::path(entryFile)); +// if (!std::filesystem::exists(entryPath)) return ""; +// else return UTF82String(entryPath.u8string()); +// } catch (...) { +// return ""; +// } +// } + +// std::string getPluginPackageName(const std::string& dirPath) { +// auto dirPath_obj = std::filesystem::path(dirPath); + +// std::filesystem::path packageFilePath = dirPath_obj / std::filesystem::path("package.json"); +// if (!std::filesystem::exists(packageFilePath)) return ""; + +// try { +// std::ifstream file(UTF82String(packageFilePath.make_preferred().u8string())); +// nlohmann::json j; +// file >> j; +// std::string packageName = ""; +// if (j.contains("name")) { +// packageName = j["name"].get(); +// } +// return packageName; +// } catch (...) { +// return ""; +// } +// } + +// bool doesPluginPackHasDependency(const std::string& dirPath) { +// auto dirPath_obj = std::filesystem::path(dirPath); + +// std::filesystem::path packageFilePath = dirPath_obj / std::filesystem::path("package.json"); +// if (!std::filesystem::exists(packageFilePath)) return false; + +// try { +// std::ifstream file(UTF82String(packageFilePath.make_preferred().u8string())); +// nlohmann::json j; +// file >> j; +// if (j.contains("dependencies")) { +// return true; +// } +// return false; +// } catch (...) { +// return false; +// } +// } + +// bool processConsoleNpmCmd(const std::string& cmd) { +// #ifdef LLSE_BACKEND_NODEJS +// if (StartsWith(cmd, "npm ")) { +// executeNpmCommand(cmd); +// return false; +// } else return true; +// #else +// return true; +// #endif +// } + +// int executeNpmCommand(std::string cmd, std::string workingDir) { +// if (!nodeJsInited && !initNodeJs()) { +// return -1; +// } +// std::vector errors; +// std::unique_ptr setup = node::CommonEnvironmentSetup::Create( +// platform.get(), +// &errors, +// args, +// exec_args, +// node::EnvironmentFlags::kOwnsProcessState +// ); +// // if kOwnsInspector set, inspector_agent.cc:681 +// // CHECK_EQ(start_io_thread_async_initialized.exchange(true), false) fail! + +// if (!setup) { +// for (const std::string& err : errors) logger.error("CommonEnvironmentSetup Error: {}", err.c_str()); +// return -1; +// } +// v8::Isolate* isolate = setup->isolate(); +// node::Environment* env = setup->env(); +// int exit_code = 0; + +// // Process workingDir +// workingDir = ReplaceStr(workingDir, "\\", "/"); + +// { +// using namespace v8; +// v8::Locker locker(isolate); +// v8::Isolate::Scope isolate_scope(isolate); +// v8::HandleScope handle_scope(isolate); +// v8::Context::Scope context_scope(setup->context()); + +// string executeJs = "const oldCwd = process.cwd();" +// "const publicRequire = require('module').createRequire(oldCwd + " +// "'/plugins/lib/');" +// "require('process').chdir('" +// + workingDir + "');" + "publicRequire('npm-js-interface')('" + cmd + "');" +// + "require('process').chdir(oldCwd);"; + +// try { +// node::SetProcessExitHandler(env, [&](node::Environment* env_, int exit_code) { node::Stop(env); }); +// MaybeLocal loadenv_ret = node::LoadEnvironment(env, executeJs.c_str()); +// if (loadenv_ret.IsEmpty()) // There has been a JS exception. +// throw "error"; +// exit_code = node::SpinEventLoop(env).FromMaybe(1); +// } catch (...) { +// logger.error("Fail to execute NPM command. Error occurs"); +// } +// } + +// node::Stop(env); +// return exit_code; +// } + +// } // namespace NodeJsHelper + +// #endif diff --git a/xmake.lua b/xmake.lua index 8ba426b..27324c7 100644 --- a/xmake.lua +++ b/xmake.lua @@ -52,6 +52,10 @@ package("scriptx") package:add("includedirs", "include/" .. backend .. "/") package:add("links", backend) package:add("links", "scriptx_" .. scriptx_backends[backend]) + + if backend == "libnode" then + package:add("includedirs", "include/libnode/v8/") -- Because ScriptX has #include in its headers. + end end) target("legacy-script-engine") @@ -101,20 +105,20 @@ target("legacy-script-engine") set_kind("shared") set_languages("cxx20") - on_load(function (target) - import("core.base.option") - - local backend = option.get("backend") - - local backend_define_suffixes = { - libnode = "NODEJS", - lua = "LUA", - python310 = "_PYTHON", - quickjs = "JS", - } - - print("Using target config: backend=" .. backend .. ", backend_define_suffix=" .. - backend_define_suffixes[backend]) - - target:add("defines", "LLSE_BACKEND_" .. backend_define_suffixes[backend]) - end) + if is_config("backend", "libnode") then + add_defines( + "LLSE_BACKEND_NODEJS" + ) + elseif is_config("backend", "lua") then + add_defines( + "LLSE_BACKEND_LUA" + ) + elseif is_config("backend", "python310") then + add_defines( + "LLSE_BACKEND_PYTHON" + ) + elseif is_config("backend", "quickjs") then + add_defines( + "LLSE_BACKEND_JS" + ) + end