#include "py_subprocess.h" #include "app_base.h" #include #include #include #include #include #include #include #ifdef _WIN32 # define WIN32_LEAN_AND_MEAN # include #else # include # include # include #endif namespace navegator { namespace { bool file_exists(const std::string& p) { if (p.empty()) return false; FILE* f = std::fopen(p.c_str(), "rb"); if (!f) return false; std::fclose(f); return true; } std::string getenv_str(const char* name) { const char* v = std::getenv(name); return v ? std::string(v) : std::string(); } } // anon std::string py_resolve_registry_root() { std::string s = getenv_str("FN_REGISTRY_ROOT"); if (!s.empty()) return s; // Fallback: deducir desde exe_dir subiendo hacia el repo. Por defecto // la app vive en projects/navegator/apps//, asi que 4 niveles // arriba esta la raiz. std::string exe = fn::exe_dir(); if (exe.empty()) return ""; // Si la app esta en Desktop\apps\\, no podemos deducir — devolver "". // El user debe setear FN_REGISTRY_ROOT en el entorno. if (exe.find("Desktop") != std::string::npos) return ""; // Subir 4 niveles. std::string p = exe; for (int i = 0; i < 4; ++i) { auto pos = p.find_last_of("/\\"); if (pos == std::string::npos) return ""; p = p.substr(0, pos); } return p; } std::string py_resolve_interpreter() { std::string root = py_resolve_registry_root(); if (!root.empty()) { #ifdef _WIN32 std::string venv_py = root + "\\python\\.venv\\Scripts\\python.exe"; if (file_exists(venv_py)) return venv_py; // Windows venv no encontrado — intentar via WSL si FN_REGISTRY_ROOT_WSL existe. std::string wsl_root = getenv_str("FN_REGISTRY_ROOT_WSL"); if (!wsl_root.empty()) return "wsl.exe"; // sentinel; py_run lo expande #else std::string venv_py = root + "/python/.venv/bin/python3"; if (file_exists(venv_py)) return venv_py; #endif } #ifdef _WIN32 return "python"; // confiar en PATH (py launcher o python.exe) #else return "python3"; #endif } // --------------------------------------------------------------------------- // Windows impl // --------------------------------------------------------------------------- #ifdef _WIN32 namespace { std::string quote_arg_win(const std::string& a) { bool need_q = a.empty() || a.find_first_of(" \t\"") != std::string::npos; if (!need_q) return a; std::string out; out.reserve(a.size() + 4); out += '"'; for (char c : a) { if (c == '"') out += "\\\""; else if (c == '\\') { out += "\\\\"; } else out += c; } out += '"'; return out; } } // anon PyResult py_run(const std::vector& argv, int timeout_ms) { PyResult res; if (argv.empty()) { res.error = "argv empty"; return res; } // Si argv[0] es el sentinel "wsl.exe", reescribir el comando para invocar // el python del venv WSL con el contexto Linux correcto: // wsl.exe --cd -- env FN_REGISTRY_ROOT= python3 -c "..." // Esto garantiza que el script Python recibe FN_REGISTRY_ROOT como path Linux, // puede importar funciones del registry y resuelve deps del venv WSL. std::vector final_argv; if (argv[0] == "wsl.exe") { std::string wsl_root = getenv_str("FN_REGISTRY_ROOT_WSL"); if (wsl_root.empty()) { res.error = "wsl.exe sentinel but FN_REGISTRY_ROOT_WSL not set"; return res; } std::string python3 = wsl_root + "/python/.venv/bin/python3"; final_argv.push_back("wsl.exe"); final_argv.push_back("--cd"); final_argv.push_back(wsl_root); final_argv.push_back("--"); final_argv.push_back("env"); final_argv.push_back("FN_REGISTRY_ROOT=" + wsl_root); final_argv.push_back(python3); // Append rest of original argv (skip argv[0] = "wsl.exe") for (size_t i = 1; i < argv.size(); ++i) final_argv.push_back(argv[i]); } else { final_argv = argv; } std::string cmd; for (size_t i = 0; i < final_argv.size(); ++i) { if (i) cmd += ' '; cmd += quote_arg_win(final_argv[i]); } HANDLE r_pipe = nullptr; HANDLE w_pipe = nullptr; SECURITY_ATTRIBUTES sa{}; sa.nLength = sizeof(sa); sa.bInheritHandle = TRUE; if (!CreatePipe(&r_pipe, &w_pipe, &sa, 0)) { res.error = "CreatePipe failed"; return res; } SetHandleInformation(r_pipe, HANDLE_FLAG_INHERIT, 0); STARTUPINFOA si{}; si.cb = sizeof(si); si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; si.wShowWindow = SW_HIDE; si.hStdOutput = w_pipe; si.hStdError = w_pipe; si.hStdInput = GetStdHandle(STD_INPUT_HANDLE); PROCESS_INFORMATION pi{}; std::string mutable_cmd = cmd; BOOL ok = CreateProcessA( nullptr, mutable_cmd.data(), nullptr, nullptr, TRUE, CREATE_NO_WINDOW, nullptr, nullptr, &si, &pi); CloseHandle(w_pipe); if (!ok) { CloseHandle(r_pipe); DWORD e = GetLastError(); char buf[128]; std::snprintf(buf, sizeof(buf), "CreateProcess failed err=%lu cmd=%s", (unsigned long)e, cmd.c_str()); res.error = buf; return res; } // Lector + timeout: spawn thread lector, wait padre con timeout. std::atomic done{false}; std::string out; std::thread reader([&]() { char buf[4096]; DWORD n = 0; while (ReadFile(r_pipe, buf, sizeof(buf), &n, nullptr) && n > 0) { out.append(buf, n); } done.store(true); }); DWORD waited = WaitForSingleObject(pi.hProcess, timeout_ms > 0 ? (DWORD)timeout_ms : INFINITE); if (waited == WAIT_TIMEOUT) { TerminateProcess(pi.hProcess, 1); res.error = "timeout"; } DWORD exit_code = 0; GetExitCodeProcess(pi.hProcess, &exit_code); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); // Pipe se cerrara cuando el proceso terminado libere los handles. // Cerrar nuestro extremo para desbloquear lector si esta colgado. CloseHandle(r_pipe); if (reader.joinable()) reader.join(); res.exit_code = (int)exit_code; res.stdout_data = std::move(out); return res; } #else // POSIX PyResult py_run(const std::vector& argv, int timeout_ms) { PyResult res; if (argv.empty()) { res.error = "argv empty"; return res; } // Build "cmd args..." via popen for simplicity. Escapado minimo. std::string cmd; for (size_t i = 0; i < argv.size(); ++i) { if (i) cmd += ' '; cmd += "'"; for (char c : argv[i]) { if (c == '\'') cmd += "'\\''"; else cmd += c; } cmd += "'"; } cmd += " 2>&1"; (void)timeout_ms; FILE* pipe = popen(cmd.c_str(), "r"); if (!pipe) { res.error = "popen failed"; return res; } std::string out; char buf[4096]; while (fgets(buf, sizeof(buf), pipe)) out.append(buf); int rc = pclose(pipe); res.exit_code = WIFEXITED(rc) ? WEXITSTATUS(rc) : -1; res.stdout_data = std::move(out); return res; } #endif PyResult py_run_inline(const std::string& code, const std::vector& extra_args, int timeout_ms) { std::vector argv; argv.push_back(py_resolve_interpreter()); argv.push_back("-c"); argv.push_back(code); for (const auto& a : extra_args) argv.push_back(a); return py_run(argv, timeout_ms); } void py_run_async(const std::vector& argv, int timeout_ms, std::function on_done) { std::thread([argv, timeout_ms, cb = std::move(on_done)]() { PyResult r = py_run(argv, timeout_ms); if (cb) cb(std::move(r)); }).detach(); } } // namespace navegator