|
9 | 9 |
|
10 | 10 | #include <filesystem>
|
11 | 11 | #include <nlohmann/json.hpp>
|
| 12 | +#include <fstream> // Include for std::ifstream |
12 | 13 |
|
13 | 14 | #if defined(__linux__) || defined(__APPLE__)
|
14 | 15 | #include <dlfcn.h>
|
@@ -325,6 +326,127 @@ struct Env {
|
325 | 326 |
|
326 | 327 | static inline std::vector<std::string> Path;
|
327 | 328 |
|
| 329 | + // Helper function to detect virtual environments and find base Python paths |
| 330 | + static std::vector<std::string> detectVenvBasePaths() { |
| 331 | + std::vector<std::string> venvBasePaths; |
| 332 | + |
| 333 | + // First, check if we're running from a virtual environment |
| 334 | + // by checking for pyvenv.cfg in any of the paths |
| 335 | + for (const auto &path : Path) { |
| 336 | + if (path.empty()) |
| 337 | + continue; |
| 338 | + |
| 339 | + namespace fs = std::filesystem; |
| 340 | + fs::path base_path = fs::path(path); |
| 341 | + |
| 342 | + // Check if this might be inside a venv by looking for the parent directories |
| 343 | + fs::path venv_root; |
| 344 | + fs::path check_path = base_path; |
| 345 | + bool found_venv = false; |
| 346 | + |
| 347 | + // Look up the directory tree for a pyvenv.cfg file (up to 3 levels) |
| 348 | + for (int i = 0; i < 3 && !found_venv; i++) { |
| 349 | + fs::path cfg_path = check_path / "pyvenv.cfg"; |
| 350 | + if (fs::exists(cfg_path)) { |
| 351 | + venv_root = check_path; |
| 352 | + found_venv = true; |
| 353 | + break; |
| 354 | + } |
| 355 | + |
| 356 | + // Also check one level up (common structure: venv/lib/pythonX.Y/site-packages) |
| 357 | + if (check_path.has_parent_path()) { |
| 358 | + check_path = check_path.parent_path(); |
| 359 | + cfg_path = check_path / "pyvenv.cfg"; |
| 360 | + if (fs::exists(cfg_path)) { |
| 361 | + venv_root = check_path; |
| 362 | + found_venv = true; |
| 363 | + break; |
| 364 | + } |
| 365 | + } else { |
| 366 | + break; |
| 367 | + } |
| 368 | + } |
| 369 | + |
| 370 | + if (found_venv) { |
| 371 | + // Found a venv, parse the pyvenv.cfg file to find the base Python |
| 372 | + fs::path cfg_path = venv_root / "pyvenv.cfg"; |
| 373 | + SHLOG_DEBUG("Found Python venv config: {}", cfg_path.string()); |
| 374 | + |
| 375 | + std::ifstream cfg_file(cfg_path.string()); |
| 376 | + if (cfg_file.is_open()) { |
| 377 | + std::string line; |
| 378 | + std::string home_path; |
| 379 | + std::string base_prefix; |
| 380 | + |
| 381 | + while (std::getline(cfg_file, line)) { |
| 382 | + // Parse key = value format |
| 383 | + auto pos = line.find('='); |
| 384 | + if (pos != std::string::npos) { |
| 385 | + std::string key = line.substr(0, pos); |
| 386 | + std::string value = line.substr(pos + 1); |
| 387 | + |
| 388 | + // Trim whitespace |
| 389 | + key.erase(0, key.find_first_not_of(" \t")); |
| 390 | + key.erase(key.find_last_not_of(" \t") + 1); |
| 391 | + value.erase(0, value.find_first_not_of(" \t")); |
| 392 | + value.erase(value.find_last_not_of(" \t") + 1); |
| 393 | + |
| 394 | + if (key == "home") { |
| 395 | + home_path = value; |
| 396 | + } else if (key == "base-prefix") { |
| 397 | + base_prefix = value; |
| 398 | + } |
| 399 | + } |
| 400 | + } |
| 401 | + |
| 402 | + // Add the home path if found |
| 403 | + if (!home_path.empty()) { |
| 404 | + fs::path home(home_path); |
| 405 | + venvBasePaths.push_back(home.string()); |
| 406 | + SHLOG_DEBUG("Found Python venv home path: {}", home.string()); |
| 407 | + |
| 408 | + // Also add common library locations relative to home |
| 409 | + fs::path lib_path = home / "lib"; |
| 410 | + if (fs::exists(lib_path) && fs::is_directory(lib_path)) { |
| 411 | + venvBasePaths.push_back(lib_path.string()); |
| 412 | + } |
| 413 | + |
| 414 | +#ifdef __APPLE__ |
| 415 | + // On macOS, also check in Frameworks directory |
| 416 | + fs::path fw_path = home / "Frameworks"; |
| 417 | + if (fs::exists(fw_path) && fs::is_directory(fw_path)) { |
| 418 | + venvBasePaths.push_back(fw_path.string()); |
| 419 | + } |
| 420 | +#endif |
| 421 | + } |
| 422 | + |
| 423 | + // Add base-prefix if found and different from home |
| 424 | + if (!base_prefix.empty() && base_prefix != home_path) { |
| 425 | + fs::path base(base_prefix); |
| 426 | + venvBasePaths.push_back(base.string()); |
| 427 | + SHLOG_DEBUG("Found Python venv base-prefix: {}", base.string()); |
| 428 | + |
| 429 | + // Also add common library locations relative to base prefix |
| 430 | + fs::path lib_path = base / "lib"; |
| 431 | + if (fs::exists(lib_path) && fs::is_directory(lib_path)) { |
| 432 | + venvBasePaths.push_back(lib_path.string()); |
| 433 | + } |
| 434 | + |
| 435 | +#ifdef __APPLE__ |
| 436 | + // On macOS, also check in Frameworks directory |
| 437 | + fs::path fw_path = base / "Frameworks"; |
| 438 | + if (fs::exists(fw_path) && fs::is_directory(fw_path)) { |
| 439 | + venvBasePaths.push_back(fw_path.string()); |
| 440 | + } |
| 441 | +#endif |
| 442 | + } |
| 443 | + } |
| 444 | + } |
| 445 | + } |
| 446 | + |
| 447 | + return venvBasePaths; |
| 448 | + } |
| 449 | + |
328 | 450 | static std::string getPythonCommand() {
|
329 | 451 | #ifdef _WIN32
|
330 | 452 | // On Windows, try python.exe first, then py.exe (Python launcher)
|
@@ -414,6 +536,12 @@ struct Env {
|
414 | 536 | } else {
|
415 | 537 | SHLOG_ERROR("Failed to execute Python path probe command: {}", cmd);
|
416 | 538 | }
|
| 539 | + |
| 540 | + // Get extra paths from venv configuration |
| 541 | + std::vector<std::string> venvBasePaths = detectVenvBasePaths(); |
| 542 | + for (const auto &path : venvBasePaths) { |
| 543 | + SHLOG_DEBUG("PY VENV BASE PATH: {}", path); |
| 544 | + } |
417 | 545 | } catch (const std::exception &ex) {
|
418 | 546 | SHLOG_ERROR("Error while probing python: {}", ex.what());
|
419 | 547 | }
|
@@ -451,6 +579,36 @@ struct Env {
|
451 | 579 | candidates.push_back(base_name);
|
452 | 580 | }
|
453 | 581 |
|
| 582 | + // Check venv paths first (priority) |
| 583 | + std::vector<std::string> venvBasePaths = detectVenvBasePaths(); |
| 584 | + for (const auto &path : venvBasePaths) { |
| 585 | + namespace fs = std::filesystem; |
| 586 | + fs::path base_path = fs::path(path); |
| 587 | + |
| 588 | + // Try in the path directly |
| 589 | + for (const auto &base_name : base_names) { |
| 590 | + candidates.push_back((base_path / base_name).string()); |
| 591 | + } |
| 592 | + |
| 593 | + // Also try in lib subdirectory if it exists |
| 594 | + fs::path lib_path = base_path / "lib"; |
| 595 | + if (fs::exists(lib_path) && fs::is_directory(lib_path)) { |
| 596 | + for (const auto &base_name : base_names) { |
| 597 | + candidates.push_back((lib_path / base_name).string()); |
| 598 | + } |
| 599 | + } |
| 600 | + |
| 601 | +#ifdef __APPLE__ |
| 602 | + // On macOS, also check in Frameworks directory |
| 603 | + fs::path fw_path = base_path / "Frameworks"; |
| 604 | + if (fs::exists(fw_path) && fs::is_directory(fw_path)) { |
| 605 | + for (const auto &base_name : base_names) { |
| 606 | + candidates.push_back((fw_path / base_name).string()); |
| 607 | + } |
| 608 | + } |
| 609 | +#endif |
| 610 | + } |
| 611 | + |
454 | 612 | // Then try all discovered Python paths
|
455 | 613 | for (const auto &path : Path) {
|
456 | 614 | if (path.empty())
|
|
0 commit comments