diff --git a/CMakeLists.txt b/CMakeLists.txt index 39798ec2..95fe4810 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -213,6 +213,9 @@ set(XEUS_CPP_SRC src/xoptions.cpp src/xparser.cpp src/xutils.cpp + + # magics + src/xmagics/pythonexec.cpp ) if(NOT EMSCRIPTEN) @@ -377,6 +380,31 @@ macro(xeus_cpp_create_target target_name linkage output_name) target_link_libraries(${target_name} PRIVATE ${CMAKE_THREAD_LIBS_INIT}) endif() + # Python Stuff + if(NOT EMSCRIPTEN) + find_package(Python COMPONENTS Interpreter Development) + + target_include_directories(${target_name} PRIVATE ${Python_INCLUDE_DIRS}) + target_link_libraries(${target_name} PUBLIC ${Python_LIBRARIES}) + target_compile_options(${target_name} PRIVATE ${Python_CFLAGS_OTHER}) + else() + set(Python_INCLUDE_DIRS + "${CMAKE_PREFIX_PATH}/include/python3.11") + set(Python_LIBRARIES + "python3.11" + "m" + "bz2" + "libz.a" + "sqlite3" + "ffi" + ) + set(Python_CFLAGS_OTHER "-sUSE_ZLIB=1" "-sUSE_BZIP2=1") + + target_include_directories(${target_name} PRIVATE ${Python_INCLUDE_DIRS}) + target_link_libraries(${target_name} PUBLIC ${Python_LIBRARIES}) + target_compile_options(${target_name} PRIVATE ${Python_CFLAGS_OTHER}) + endif() + endmacro() # xeus-cpp-headers @@ -437,6 +465,7 @@ endif() if(EMSCRIPTEN) include(WasmBuildOptions) find_package(xeus-lite REQUIRED) + link_directories(BEFORE "${CMAKE_INSTALL_PREFIX}/lib") add_executable(xcpp src/main_emscripten_kernel.cpp ) target_link_libraries(xcpp PRIVATE xeus-lite) xeus_cpp_set_kernel_options(xcpp) @@ -444,11 +473,20 @@ if(EMSCRIPTEN) xeus_wasm_link_options(xcpp "web,worker") # TODO: Remove the exported runtime methods # after the next xeus release. + # , \"ffi_call\", \"ffi_closure_alloc\", \"ffi_prep_cif\", \"ffi_prep_cif_var\", \"ffi_prep_closure_loc\", \"ffi_type_double\", \"ffi_type_float\", \"ffi_type_longdouble\", \"ffi_type_pointer\", \"ffi_type_sint16\", \"ffi_type_sint32\", \"ffi_type_sint64\", \"ffi_type_sint8\", \"ffi_type_uint16\", \"ffi_type_uint32\", \"ffi_type_uint64\", \"ffi_type_uint8\", \"ffi_type_void\" target_link_options(xcpp PUBLIC "SHELL: -s EXPORTED_RUNTIME_METHODS='[\"FS\",\"PATH\",\"LDSO\",\"loadDynamicLibrary\",\"ERRNO_CODES\"]'" PUBLIC "SHELL: --preload-file ${SYSROOT_PATH}/include@/include" + PUBLIC "SHELL: --preload-file ${CPyCppyy_DIR}/include/CPyCppyy@/include/CPyCppyy" + PUBLIC "SHELL: --preload-file ${CPyCppyy_BUILD_DIR}/libcppyy.so@/lib/python3.11/site-packages/libcppyy.so" + PUBLIC "SHELL: --preload-file ${Cppyy_Backend_DIR}/python/cppyy_backend@/lib/python3.11/site-packages/cppyy_backend" + PUBLIC "SHELL: --preload-file ${Cppyy_DIR}/python/cppyy@/lib/python3.11/site-packages/cppyy" + PUBLIC "SHELL: --preload-file ${Cppyy_DIR}/python/cppyy_compat@/lib/python3.11/site-packages/cppyy_compat" + PUBLIC "SHELL: --preload-file ${CMAKE_PREFIX_PATH}/lib/python3.11@/lib/python3.11" + PUBLIC "SHELL: --preload-file ${CMAKE_PREFIX_PATH}/include/clang@/include/clang" + PUBLIC "SHELL: --preload-file ${CMAKE_PREFIX_PATH}/include/clang-c@/include/clang-c" PUBLIC "SHELL: --post-js ${CMAKE_CURRENT_SOURCE_DIR}/wasm_patches/post.js" - ) + ) # TODO: Emscripten supports preloading files just once before it generates # the xcpp.data file (containing the binary representation of the file(s) we # want to include in our application). diff --git a/environment-wasm-host.yml b/environment-wasm-host.yml index da8170f5..a7ad0f1e 100644 --- a/environment-wasm-host.yml +++ b/environment-wasm-host.yml @@ -9,3 +9,8 @@ dependencies: - CppInterOp>=1.5.0 - cpp-argparse - pugixml + - python=3.11 + - bzip2 + - zlib + - libffi + - sqlite diff --git a/src/xinterpreter.cpp b/src/xinterpreter.cpp index 5ed527e3..eda73f12 100644 --- a/src/xinterpreter.cpp +++ b/src/xinterpreter.cpp @@ -21,6 +21,7 @@ #include "xmagics/os.hpp" #include "xmagics/xassist.hpp" #endif +#include "xmagics/pythonexec.hpp" #include "xparser.hpp" #include "xsystem.hpp" @@ -371,7 +372,7 @@ __get_cxx_version () // executable(m_interpreter)); // preamble_manager["magics"].get_cast().register_magic("timeit", // timeit(&m_interpreter)); - // preamble_manager["magics"].get_cast().register_magic("python", pythonexec()); + preamble_manager["magics"].get_cast().register_magic("python", pythonexec()); #ifndef EMSCRIPTEN preamble_manager["magics"].get_cast().register_magic("xassist", xassist()); preamble_manager["magics"].get_cast().register_magic("file", writefile()); diff --git a/src/xmagics/pythonexec.cpp b/src/xmagics/pythonexec.cpp new file mode 100644 index 00000000..895f6f02 --- /dev/null +++ b/src/xmagics/pythonexec.cpp @@ -0,0 +1,86 @@ +/************************************************************************************ + * Copyright (c) 2025, xeus-cpp contributors * + * * + * Distributed under the terms of the BSD 3-Clause License. * + * * + * The full license is in the file LICENSE, distributed with this software. * + ************************************************************************************/ + +#include "pythonexec.hpp" +#include "../xparser.hpp" +#include + +#include "Python.h" + +#include "clang/Interpreter/CppInterOp.h" + +namespace xcpp { + bool pythonexec::is_initalized = false; + + void pythonexec::operator()([[maybe_unused]] const std::string& line, const std::string& cell) { + + if (!is_initalized) + initialize(); + if (!is_initalized) { + // initializing failed + std::cout << Cpp::EndStdStreamCapture(); + std::cerr << Cpp::EndStdStreamCapture(); + + std::cerr << "Failed to Initialize Python\n"; + return; + } + + std::string code = trim(cell); + if (code.empty()) + return; + + Cpp::BeginStdStreamCapture(Cpp::kStdErr); + Cpp::BeginStdStreamCapture(Cpp::kStdOut); + + PyRun_SimpleString(code.c_str()); + + std::cout << Cpp::EndStdStreamCapture(); + std::cerr << Cpp::EndStdStreamCapture(); + } + + void pythonexec::initialize() { +#ifdef EMSCRIPTEN + PyStatus status; + + PyConfig config; + PyConfig_InitPythonConfig(&config); + static const std::wstring prefix(L"/"); + config.base_prefix = const_cast(prefix.c_str()); + config.base_exec_prefix = const_cast(prefix.c_str()); + config.prefix = const_cast(prefix.c_str()); + config.exec_prefix = const_cast(prefix.c_str()); + + status = Py_InitializeFromConfig(&config); + if (PyStatus_Exception(status)) { + // TODO: initialization failed, propagate error + PyConfig_Clear(&config); + is_initalized = false; + return; + } + PyConfig_Clear(&config); +#else + Py_Initialize(); +#endif + + PyRun_SimpleString("import sys\nsys.path.append('')"); // add current directory to PYTHONPATH + + // // Import cppyy module + // PyObject* cppyyModule = PyImport_ImportModule("cppyy"); + // if (!cppyyModule) { + // PyErr_Print(); + // Py_Finalize(); + // return; // Handle import error as needed + // } + + // PyObject* mainModule = PyImport_AddModule("__main__"); + // PyObject_SetAttrString(mainModule, "cppyy", cppyyModule); + // Py_XDECREF(cppyyModule); + + is_initalized = true; + } +} // namespace xcpp diff --git a/src/xmagics/pythonexec.hpp b/src/xmagics/pythonexec.hpp new file mode 100644 index 00000000..3cbf1a18 --- /dev/null +++ b/src/xmagics/pythonexec.hpp @@ -0,0 +1,30 @@ +/************************************************************************************ + * Copyright (c) 2025, xeus-cpp contributors * + * * + * Distributed under the terms of the BSD 3-Clause License. * + * * + * The full license is in the file LICENSE, distributed with this software. * + ************************************************************************************/ + +#ifndef XEUS_CPP_PYTHONEXEC_MAGIC_HPP +#define XEUS_CPP_PYTHONEXEC_MAGIC_HPP + +#include + +#include "xeus-cpp/xmagics.hpp" + +namespace xcpp +{ + class pythonexec : public xmagic_cell + { + public: + XEUS_CPP_API + void operator()(const std::string& line, const std::string& cell) override; + private: + static bool is_initalized; + void initialize(); + + }; +} // namespace xcpp + +#endif //XEUS_CPP_PYTHONEXEC_MAGIC_HPP