Files
navegator_dashboard/py_subprocess.cpp
T

269 lines
7.9 KiB
C++

#include "py_subprocess.h"
#include "app_base.h"
#include <atomic>
#include <chrono>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <sstream>
#include <thread>
#ifdef _WIN32
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
#else
# include <sys/types.h>
# include <sys/wait.h>
# include <unistd.h>
#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/<app>/, 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\<a>\, 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<std::string>& 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 <linux_root> -- env FN_REGISTRY_ROOT=<linux_root> python3 -c "..." <args>
// 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<std::string> 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<bool> 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<std::string>& 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<std::string>& extra_args,
int timeout_ms) {
std::vector<std::string> 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<std::string>& argv, int timeout_ms,
std::function<void(PyResult)> 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