Skip to content

Add support for Windows in the Jupyter kernel for Cling #19369

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
71 changes: 70 additions & 1 deletion interpreter/cling/tools/Jupyter/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ set(LLVM_NO_DEAD_STRIP 1)
set(SOURCES
Kernel.cpp
)
set_source_files_properties(Kernel.cpp COMPILE_FLAGS "-fexceptions -frtti")
if(MSVC)
set_source_files_properties(Kernel.cpp COMPILE_FLAGS "/EHsc /GR")
else()
set_source_files_properties(Kernel.cpp COMPILE_FLAGS "-fexceptions -frtti")
endif()
#Solve unresolved symbols bug in unix
#See https://github.com/vgvassilev/cling/issues/114
if(WIN32)
Expand Down Expand Up @@ -81,6 +85,71 @@ add_cling_library(libclingJupyter ${ENABLE_SHARED} ${ENABLE_STATIC}
set_target_properties(libclingJupyter
PROPERTIES ENABLE_EXPORTS 1)

if(MSVC)
set_target_properties(libclingJupyter PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS 1)

# RTTI/C++ symbols
set(jupyter_exports ${jupyter_exports} ??_7type_info@@6B@
?__type_info_root_node@@3U__type_info_node@@A
?nothrow@std@@3Unothrow_t@1@B
__std_find_trivial_1
__std_reverse_trivially_swappable_1
)

# Compiler added symbols for static variables. NOT for VStudio < 2015
set(jupyter_exports ${jupyter_exports} _Init_thread_abort _Init_thread_epoch
_Init_thread_footer _Init_thread_header _tls_index
)

if(CMAKE_SIZEOF_VOID_P EQUAL 8)
# new/delete variants needed when linking to static msvc runtime (esp. Debug)
set(jupyter_exports ${jupyter_exports}
??2@YAPEAX_K@Z
??3@YAXPEAX@Z
??_U@YAPEAX_K@Z
??_V@YAXPEAX@Z
??3@YAXPEAX_K@Z
??2@YAPEAX_KAEBUnothrow_t@std@@@Z
??_U@YAPEAX_KAEBUnothrow_t@std@@@Z
??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QEAAAEAV01@H@Z
??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QEAAAEAV01@M@Z
??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QEAAAEAV01@N@Z
??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QEAAAEAV01@PEBX@Z
??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QEAAAEAV01@P6AAEAV01@AEAV01@@Z@Z
??$?6U?$char_traits@D@std@@@std@@YAAEAV?$basic_ostream@DU?$char_traits@D@std@@@0@AEAV10@D@Z
??$?6U?$char_traits@D@std@@@std@@YAAEAV?$basic_ostream@DU?$char_traits@D@std@@@0@AEAV10@PEBD@Z
?_Facet_Register@std@@YAXPEAV_Facet_base@1@@Z
)
else()
set(jupyter_exports ${jupyter_exports}
??2@YAPAXI@Z
??3@YAXPAX@Z
??3@YAXPAXI@Z
??_U@YAPAXI@Z
??_V@YAXPAX@Z
??_V@YAXPAXI@Z
??2@YAPAXIABUnothrow_t@std@@@Z
??_U@YAPAXIABUnothrow_t@std@@@Z
??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@H@Z
??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@M@Z
??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@N@Z
??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@PBX@Z
??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@P6AAAV01@AAV01@@Z@Z
??$?6U?$char_traits@D@std@@@std@@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@0@AAV10@D@Z
??$?6U?$char_traits@D@std@@@std@@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@0@AAV10@PBD@Z
?_Facet_Register@std@@YAXPAV_Facet_base@1@@Z
)
endif()

# List to '/EXPORT:sym0 /EXPORT:sym1 /EXPORT:sym2 ...'
foreach(sym ${jupyter_exports})
set(cling_link_str "${cling_link_str} /EXPORT:${sym}")
endforeach(sym ${jupyter_exports})

set_property(TARGET libclingJupyter APPEND_STRING PROPERTY LINK_FLAGS ${cling_link_str})

endif(MSVC)

if(ENABLE_SHARED)
if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
set(LIBCLINGJUPYTER_LINK_FLAGS " -Wl,-compatibility_version -Wl,1")
Expand Down
97 changes: 83 additions & 14 deletions interpreter/cling/tools/Jupyter/kernel/clingkernel.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,36 +17,65 @@

__version__ = '0.0.3'

import ctypes
from contextlib import contextmanager
from fcntl import fcntl, F_GETFL, F_SETFL
import os
import shutil
import select
import struct
import sys
import threading
import time

from traitlets import Unicode, Float, Dict, List, CaselessStrEnum
from ipykernel.kernelbase import Kernel
from ipykernel.kernelapp import kernel_aliases,kernel_flags, IPKernelApp
from ipykernel.ipkernel import IPythonKernel
from ipykernel.zmqshell import ZMQInteractiveShell
from IPython.core.profiledir import ProfileDir
from jupyter_client.session import Session

Check failure on line 35 in interpreter/cling/tools/Jupyter/kernel/clingkernel.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (I001)

interpreter/cling/tools/Jupyter/kernel/clingkernel.py:20:1: I001 Import block is un-sorted or un-formatted


class my_void_p(ctypes.c_void_p):
pass

libc = ctypes.CDLL(None)
try:
c_stdout_p = ctypes.c_void_p.in_dll(libc, 'stdout')
c_stderr_p = ctypes.c_void_p.in_dll(libc, 'stderr')
except ValueError:
# libc.stdout is has a funny name on OS X
c_stdout_p = ctypes.c_void_p.in_dll(libc, '__stdoutp')
c_stderr_p = ctypes.c_void_p.in_dll(libc, '__stderrp')
if sys.platform == 'win32':
import msvcrt
libc = ctypes.cdll.msvcrt

class FILE(ctypes.Structure):
pass

FILE_p = ctypes.POINTER(FILE)

libc._fdopen.argtypes = [ctypes.c_int, ctypes.c_char_p]
libc._fdopen.restype = FILE_p

c_stdout_p = libc._fdopen(sys.stdout.fileno(), b"w")
c_stderr_p = libc._fdopen(sys.stderr.fileno(), b"w")

peek_named_pipe = ctypes.windll.kernel32.PeekNamedPipe
peek_named_pipe.argtypes = [
ctypes.wintypes.HANDLE,
ctypes.c_void_p,
ctypes.wintypes.DWORD,
ctypes.POINTER(ctypes.wintypes.DWORD),
ctypes.POINTER(ctypes.wintypes.DWORD),
ctypes.POINTER(ctypes.wintypes.DWORD),
]
peek_named_pipe.restype = ctypes.c_bool

libc.fflush.argtypes = [FILE_p]
libc.fflush.restype = ctypes.c_int
else:
libc = ctypes.CDLL(None)

try:
c_stdout_p = ctypes.c_void_p.in_dll(libc, 'stdout')
c_stderr_p = ctypes.c_void_p.in_dll(libc, 'stderr')
except ValueError:
# libc.stdout is has a funny name on OS X
c_stdout_p = ctypes.c_void_p.in_dll(libc, '__stdoutp')
c_stderr_p = ctypes.c_void_p.in_dll(libc, '__stderrp')


class FdReplacer:
Expand All @@ -59,8 +88,9 @@
os.dup2(pipe_in, self.real_fd)
os.close(pipe_in)
# make pipe_out non-blocking
flags = fcntl(self.pipe_out, F_GETFL)
fcntl(self.pipe_out, F_SETFL, flags|os.O_NONBLOCK)
# flags = fcntl(self.pipe_out, F_GETFL)
# fcntl(self.pipe_out, F_SETFL, flags|os.O_NONBLOCK)
os.set_blocking(self.pipe_out, False)

def restore(self):
os.close(self.real_fd)
Expand Down Expand Up @@ -91,7 +121,7 @@

std = CaselessStrEnum(default_value='c++11',
values = ['c++11', 'c++14', 'c++1z', 'c++17', 'c++20', 'c++2b', 'c++23' ],
help="C++ standard to use, either c++23, c++2b, c++20, c++17, c++1z, c++14 or c++11").tag(config=True);

Check failure on line 124 in interpreter/cling/tools/Jupyter/kernel/clingkernel.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (E703)

interpreter/cling/tools/Jupyter/kernel/clingkernel.py:124:115: E703 Statement ends with an unnecessary semicolon

def __init__(self, **kwargs):
super(ClingKernel, self).__init__(**kwargs)
Expand Down Expand Up @@ -120,7 +150,7 @@
else:
raise RuntimeError('cling at ' + clingInPath + ' is unusable. No cling, no fun.')

for libFolder in ["/lib/libclingJupyter.", "/libexec/lib/libclingJupyter."]:
for libFolder in ["/bin/libclingJupyter.", "/lib/libclingJupyter.", "/libexec/lib/libclingJupyter."]:

for ext in ['so', 'dylib', 'dll']:
libFilename = clingInstDir + libFolder + ext
Expand Down Expand Up @@ -225,6 +255,44 @@

def handle_input(self):
"""Capture stdout, stderr and sideband. Forward them as stream messages."""
if sys.platform == 'win32':
pipes = {"sideband": self.sideband_pipe}
for rs in self.replaced_streams:
if rs:
pipes[rs.name] = rs.pipe_out

# wait for the flush interval before peeking at the pipe
time.sleep(self.flush_interval)

pipe_bytes = {}
total_bytes = 0
for name, pipe in pipes.items():
bytes_available = ctypes.wintypes.DWORD(0)
peek_named_pipe(
ctypes.wintypes.HANDLE(msvcrt.get_osfhandle(pipe)),
None,
0,
None,
ctypes.byref(bytes_available),
None,
)
pipe_bytes[name] = bytes_available.value
total_bytes += bytes_available.value

if total_bytes == 0:
libc.fflush(c_stdout_p)
libc.fflush(c_stderr_p)
return False

for name, n_bytes in pipe_bytes.items():
if n_bytes == 0:
continue

if name == "sideband":
self._process_sideband_data()
else:
self._process_stdio_data(pipes[name], name)
return True
# create pipe for stdout, stderr
select_on = [self.sideband_pipe]
for rs in self.replaced_streams:
Expand Down Expand Up @@ -287,7 +355,8 @@
run_cell_thread.join()

# Any leftovers?
while self.handle_input(): True
while self.handle_input():
pass

self.close_forwards()
status = 'ok'
Expand Down
7 changes: 5 additions & 2 deletions interpreter/cling/tools/Jupyter/kernel/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
# Minimal Python version sanity check
#-----------------------------------------------------------------------------

import sys

Check failure on line 21 in interpreter/cling/tools/Jupyter/kernel/setup.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (E402)

interpreter/cling/tools/Jupyter/kernel/setup.py:21:1: E402 Module level import not at top of file

v = sys.version_info
if v[0] >= 3 and v[:2] < (3,3):
Expand All @@ -30,10 +30,9 @@
# get on with it
#-----------------------------------------------------------------------------

import os

Check failure on line 33 in interpreter/cling/tools/Jupyter/kernel/setup.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (E402)

interpreter/cling/tools/Jupyter/kernel/setup.py:33:1: E402 Module level import not at top of file
from glob import glob

from distutils.core import setup

Check failure on line 35 in interpreter/cling/tools/Jupyter/kernel/setup.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (E402)

interpreter/cling/tools/Jupyter/kernel/setup.py:35:1: E402 Module level import not at top of file

Check failure on line 35 in interpreter/cling/tools/Jupyter/kernel/setup.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (I001)

interpreter/cling/tools/Jupyter/kernel/setup.py:33:1: I001 Import block is un-sorted or un-formatted

pjoin = os.path.join
here = os.path.abspath(os.path.dirname(__file__))
Expand All @@ -44,7 +43,11 @@
name = name,
version = '0.0.2',
py_modules = ['clingkernel'],
scripts = glob(pjoin('scripts', '*')),
entry_points = {
'console_scripts': [
'jupyter-cling-kernel=clingkernel:main'
],
},
description = "C++ Kernel for Jupyter with Cling",
author = 'Min RK, Axel Naumann',
author_email = '[email protected]',
Expand All @@ -64,7 +67,7 @@
)

if 'develop' in sys.argv or any(a.startswith('bdist') for a in sys.argv):
import setuptools

Check failure on line 70 in interpreter/cling/tools/Jupyter/kernel/setup.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (F401)

interpreter/cling/tools/Jupyter/kernel/setup.py:70:12: F401 `setuptools` imported but unused

setuptools_args = {}
install_requires = setuptools_args['install_requires'] = [
Expand Down
4 changes: 4 additions & 0 deletions interpreter/cling/tools/libcling/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ add_cling_library(libcling ${ENABLE_SHARED} ${ENABLE_STATIC}
set_target_properties(libcling
PROPERTIES ENABLE_EXPORTS 1)

if(MSVC)
set_target_properties(libcling PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS 1)
endif()

if(ENABLE_SHARED)
if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
set(LIBCLING_LINK_FLAGS " -Wl,-compatibility_version -Wl,1")
Expand Down
Loading