1
1
#ifndef CB_PYTHON_HPP
2
2
#define CB_PYTHON_HPP
3
3
4
- // must be on top
5
- #ifndef __kernel_entry
6
- #define __kernel_entry
7
- #endif
8
- #include < boost/process.hpp>
9
-
10
4
#include < shards/core/platform.hpp>
11
5
#include < shards/core/module.hpp>
12
6
#include < shards/core/foundation.hpp>
20
14
#include < dlfcn.h>
21
15
#endif
22
16
17
+ // For popen
18
+ #include < cstdio>
19
+ #include < array>
20
+
23
21
// define ssize_t on windows
24
22
#if _WIN32
25
23
#include < BaseTsd.h>
@@ -327,24 +325,94 @@ struct Env {
327
325
328
326
static inline std::vector<std::string> Path;
329
327
328
+ static std::string getPythonCommand () {
329
+ #ifdef _WIN32
330
+ // On Windows, try python.exe first, then py.exe (Python launcher)
331
+ std::array<char , 1024 > buffer;
332
+ FILE *pipe = _popen (" where python.exe" , " r" );
333
+ if (pipe ) {
334
+ if (fgets (buffer.data (), buffer.size (), pipe ) != nullptr ) {
335
+ _pclose (pipe );
336
+ return " python.exe" ;
337
+ }
338
+ _pclose (pipe );
339
+ }
340
+
341
+ pipe = _popen (" where py.exe" , " r" );
342
+ if (pipe ) {
343
+ if (fgets (buffer.data (), buffer.size (), pipe ) != nullptr ) {
344
+ _pclose (pipe );
345
+ return " py.exe -3" ;
346
+ }
347
+ _pclose (pipe );
348
+ }
349
+
350
+ return " python.exe" ; // fallback
351
+ #else
352
+ // On Unix-like systems, try python3 first, then python
353
+ std::array<char , 1024 > buffer;
354
+ FILE *pipe = popen (" which python3" , " r" );
355
+ if (pipe ) {
356
+ if (fgets (buffer.data (), buffer.size (), pipe ) != nullptr ) {
357
+ pclose (pipe );
358
+ return " python3" ;
359
+ }
360
+ pclose (pipe );
361
+ }
362
+
363
+ pipe = popen (" which python" , " r" );
364
+ if (pipe ) {
365
+ if (fgets (buffer.data (), buffer.size (), pipe ) != nullptr ) {
366
+ pclose (pipe );
367
+ return " python" ;
368
+ }
369
+ pclose (pipe );
370
+ }
371
+
372
+ return " python3" ; // fallback
373
+ #endif
374
+ }
375
+
330
376
static void init () {
331
377
try {
332
- // let's hack and find python paths...
333
- boost::process::ipstream opipe;
334
- boost::process::child cmd (" python -c \" import sys; print(sys.path)\" " , boost::process::std_out > opipe);
335
- cmd.join ();
336
- if (cmd.exit_code () == 0 ) {
337
- std::stringstream ss;
338
- auto s = opipe.rdbuf ();
339
- ss << s;
340
- auto paths_str = ss.str ();
341
- std::replace (paths_str.begin (), paths_str.end (), ' \' ' , ' \" ' );
342
- auto jpaths = nlohmann::json::parse (paths_str);
343
- std::vector<std::string> paths = jpaths;
344
- for (auto &path : paths) {
345
- SHLOG_DEBUG (" PY PATH: {}" , path);
378
+ // Let's probe python paths using popen instead of Boost.Process
379
+ std::array<char , 4096 > buffer; // Increased buffer size
380
+ std::string paths_str;
381
+
382
+ std::string cmd = getPythonCommand () + " -c \" import sys, json; print(json.dumps(sys.path))\" " ;
383
+
384
+ #ifdef _WIN32
385
+ FILE *pipe = _popen (cmd.c_str (), " r" );
386
+ #else
387
+ FILE *pipe = popen (cmd.c_str (), " r" );
388
+ #endif
389
+
390
+ if (pipe ) {
391
+ while (fgets (buffer.data (), buffer.size (), pipe ) != nullptr ) {
392
+ paths_str += buffer.data ();
346
393
}
347
- Path = paths;
394
+
395
+ #ifdef _WIN32
396
+ _pclose (pipe );
397
+ #else
398
+ pclose (pipe );
399
+ #endif
400
+
401
+ if (!paths_str.empty ()) {
402
+ try {
403
+ // No need to replace quotes since we're using json.dumps in Python
404
+ auto jpaths = nlohmann::json::parse (paths_str);
405
+ std::vector<std::string> paths = jpaths;
406
+ for (auto &path : paths) {
407
+ SHLOG_DEBUG (" PY PATH: {}" , path);
408
+ }
409
+ Path = paths;
410
+ } catch (const nlohmann::json::exception &e) {
411
+ SHLOG_ERROR (" Failed to parse Python paths: {}" , e.what ());
412
+ }
413
+ }
414
+ } else {
415
+ SHLOG_ERROR (" Failed to execute Python path probe command: {}" , cmd);
348
416
}
349
417
} catch (const std::exception &ex) {
350
418
SHLOG_ERROR (" Error while probing python: {}" , ex.what ());
@@ -353,28 +421,79 @@ struct Env {
353
421
static const auto version_patterns = {" 3.13" , " 313" , " 3.12" , " 312" , " 3.11" , " 311" , " 3.10" , " 310" ,
354
422
" 3.9" , " 39" , " 3.8" , " 38" , " 3.7" , " 37" , " 3" , " " };
355
423
std::vector<std::string> candidates;
356
- // prefer this order
424
+
425
+ // Build base filenames first
426
+ std::vector<std::string> base_names;
357
427
#ifdef _WIN32
358
428
for (auto &pattern : version_patterns) {
359
- candidates .emplace_back (std::string (" python" ) + pattern + " .dll" );
429
+ base_names .emplace_back (std::string (" python" ) + pattern + " .dll" );
360
430
}
361
431
for (auto &pattern : version_patterns) {
362
- candidates .emplace_back (std::string (" libpython" ) + pattern + " .dll" );
363
- candidates .emplace_back (std::string (" libpython" ) + pattern + " m.dll" );
432
+ base_names .emplace_back (std::string (" libpython" ) + pattern + " .dll" );
433
+ base_names .emplace_back (std::string (" libpython" ) + pattern + " m.dll" );
364
434
}
365
435
#elif defined(__APPLE__)
366
436
for (auto &pattern : version_patterns) {
367
- candidates .emplace_back (std::string (" libpython" ) + pattern + " .dylib" );
368
- candidates .emplace_back (std::string (" libpython" ) + pattern + " m.dylib" );
437
+ base_names .emplace_back (std::string (" libpython" ) + pattern + " .dylib" );
438
+ base_names .emplace_back (std::string (" libpython" ) + pattern + " m.dylib" );
369
439
}
370
440
#else
371
441
for (auto &pattern : version_patterns) {
372
- candidates.emplace_back (std::string (" libpython" ) + pattern + " .so" );
373
- candidates.emplace_back (std::string (" libpython" ) + pattern + " .so.1" );
374
- candidates.emplace_back (std::string (" libpython" ) + pattern + " m.so" );
375
- candidates.emplace_back (std::string (" libpython" ) + pattern + " m.so.1" );
442
+ base_names.emplace_back (std::string (" libpython" ) + pattern + " .so" );
443
+ base_names.emplace_back (std::string (" libpython" ) + pattern + " .so.1" );
444
+ base_names.emplace_back (std::string (" libpython" ) + pattern + " m.so" );
445
+ base_names.emplace_back (std::string (" libpython" ) + pattern + " m.so.1" );
446
+ }
447
+ #endif
448
+
449
+ // First add system library paths (empty path for system default locations)
450
+ for (const auto &base_name : base_names) {
451
+ candidates.push_back (base_name);
376
452
}
453
+
454
+ // Then try all discovered Python paths
455
+ for (const auto &path : Path) {
456
+ if (path.empty ())
457
+ continue ;
458
+
459
+ namespace fs = std::filesystem;
460
+ fs::path base_path = fs::path (path);
461
+
462
+ // Try in the python path directly
463
+ for (const auto &base_name : base_names) {
464
+ candidates.push_back ((base_path / base_name).string ());
465
+ }
466
+
467
+ // Check if this is a python version specific directory (e.g. python3.12)
468
+ std::string dirname = base_path.filename ().string ();
469
+ if (dirname.starts_with (" python3" )) {
470
+ // Also check the parent directory (common in conda/mamba environments)
471
+ if (base_path.has_parent_path ()) {
472
+ fs::path parent_path = base_path.parent_path ();
473
+ for (const auto &base_name : base_names) {
474
+ candidates.push_back ((parent_path / base_name).string ());
475
+ }
476
+ }
477
+ }
478
+
479
+ // Also try in lib subdirectory if it exists
480
+ fs::path lib_path = base_path / " lib" ;
481
+ if (fs::exists (lib_path) && fs::is_directory (lib_path)) {
482
+ for (const auto &base_name : base_names) {
483
+ candidates.push_back ((lib_path / base_name).string ());
484
+ }
485
+ }
486
+
487
+ #ifdef __APPLE__
488
+ // On macOS, also check in Frameworks directory
489
+ fs::path fw_path = base_path / " Frameworks" ;
490
+ if (fs::exists (fw_path) && fs::is_directory (fw_path)) {
491
+ for (const auto &base_name : base_names) {
492
+ candidates.push_back ((fw_path / base_name).string ());
493
+ }
494
+ }
377
495
#endif
496
+ }
378
497
379
498
SHLOG_TRACE (" Probing python versions: {}" , candidates.size ());
380
499
@@ -516,6 +635,32 @@ struct Env {
516
635
assert (result != nullptr );
517
636
return result;
518
637
}
638
+ case SHType::Seq: {
639
+ // Create a new Python list
640
+ auto size = var.payload .seqValue .len ;
641
+ PyObject *list = _tupleNew (size);
642
+ if (list == nullptr ) {
643
+ throw SHException (" Failed to create Python list for sequence!" );
644
+ }
645
+
646
+ // Convert each element in the sequence
647
+ size_t idx = 0 ;
648
+ for (const auto &subVar : var) {
649
+ PyObject *item = var2Py (subVar);
650
+ if (item == nullptr ) {
651
+ // Clean up on error
652
+ list->refcount --;
653
+ if (list->refcount == 0 && list->type ) {
654
+ list->type ->dealloc (list);
655
+ }
656
+ throw SHException (" Failed to convert sequence element to Python object!" );
657
+ }
658
+ // PyTuple_SetItem steals the reference, no need to decref item
659
+ _tupleSetItem (list, idx++, item);
660
+ }
661
+
662
+ return list;
663
+ }
519
664
case SHType::Int2: {
520
665
PyObject *result = _buildValue (" (LL)" , var.payload .int2Value [0 ], var.payload .int2Value [1 ]);
521
666
assert (result != nullptr );
@@ -842,7 +987,7 @@ struct Env {
842
987
auto &str = std::get<0 >(tstr);
843
988
if (str.size () > 3 && str.substr (str.size () - 3 , 3 ) == " Seq" ) {
844
989
auto &inner = innerInfos.emplace_back (SHTypeInfo{Env::toSHType (str.substr (0 , str.size () - 3 ))});
845
- auto seqType = types.emplace_back (SHTypeInfo{SHType::Seq});
990
+ auto & seqType = types.emplace_back (SHTypeInfo{SHType::Seq});
846
991
seqType.seqTypes = {&inner, 1 , 0 };
847
992
} else {
848
993
types.emplace_back (SHTypeInfo{Env::toSHType (str)});
0 commit comments