4164f5adfc
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
269 lines
7.9 KiB
C++
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
|