comparison jupyter_CbC_kernel/kernel.py @ 82:70e6b10d9220 remote/aka

rename CbC kernel
author musou_aka <>
date Sat, 23 Jun 2018 16:11:53 +0900
parents jupyter_c_kernel/kernel.py@40c903dde893
children 371a7388a93a
comparison
equal deleted inserted replaced
81:a36609a3f8b6 82:70e6b10d9220
1 from queue import Queue
2 from threading import Thread
3
4 from ipykernel.kernelbase import Kernel
5 import re
6 import subprocess
7 import tempfile
8 import os
9 import os.path as path
10
11
12 class RealTimeSubprocess(subprocess.Popen):
13 """
14 A subprocess that allows to read its stdout and stderr in real time
15 """
16
17 def __init__(self, cmd, write_to_stdout, write_to_stderr):
18 """
19 :param cmd: the command to execute
20 :param write_to_stdout: a callable that will be called with chunks of data from stdout
21 :param write_to_stderr: a callable that will be called with chunks of data from stderr
22 """
23 self._write_to_stdout = write_to_stdout
24 self._write_to_stderr = write_to_stderr
25
26 super().__init__(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=0)
27
28 self._stdout_queue = Queue()
29 self._stdout_thread = Thread(target=RealTimeSubprocess._enqueue_output, args=(self.stdout, self._stdout_queue))
30 self._stdout_thread.daemon = True
31 self._stdout_thread.start()
32
33 self._stderr_queue = Queue()
34 self._stderr_thread = Thread(target=RealTimeSubprocess._enqueue_output, args=(self.stderr, self._stderr_queue))
35 self._stderr_thread.daemon = True
36 self._stderr_thread.start()
37
38 @staticmethod
39 def _enqueue_output(stream, queue):
40 """
41 Add chunks of data from a stream to a queue until the stream is empty.
42 """
43 for line in iter(lambda: stream.read(4096), b''):
44 queue.put(line)
45 stream.close()
46
47 def write_contents(self):
48 """
49 Write the available content from stdin and stderr where specified when the instance was created
50 :return:
51 """
52
53 def read_all_from_queue(queue):
54 res = b''
55 size = queue.qsize()
56 while size != 0:
57 res += queue.get_nowait()
58 size -= 1
59 return res
60
61 stdout_contents = read_all_from_queue(self._stdout_queue)
62 if stdout_contents:
63 self._write_to_stdout(stdout_contents)
64 stderr_contents = read_all_from_queue(self._stderr_queue)
65 if stderr_contents:
66 self._write_to_stderr(stderr_contents)
67
68
69 class CbCKernel(Kernel):
70 implementation = 'jupyter_CbC_kernel'
71 implementation_version = '1.0'
72 language = 'CbC'
73 language_version = 'C11'
74 language_info = {'name': 'CbC',
75 'mimetype': 'text/plain',
76 'file_extension': '.c'}
77 banner = "CbC kernel.\n" \
78 "Uses gcc, compiles in C11, and creates source code files and executables in temporary folder.\n"
79
80 def __init__(self, *args, **kwargs):
81 super(CbCKernel, self).__init__(*args, **kwargs)
82 self.files = []
83 mastertemp = tempfile.mkstemp(suffix='.out')
84 os.close(mastertemp[0])
85 self.master_path = mastertemp[1]
86 filepath = path.join(path.dirname(path.realpath(__file__)), 'resources', 'master.c')
87 subprocess.call(['gcc', filepath, '-std=c11', '-rdynamic', '-ldl', '-o', self.master_path])
88
89 def cleanup_files(self):
90 """Remove all the temporary files created by the kernel"""
91 for file in self.files:
92 os.remove(file)
93 os.remove(self.master_path)
94
95 def new_temp_file(self, **kwargs):
96 """Create a new temp file to be deleted when the kernel shuts down"""
97 # We don't want the file to be deleted when closed, but only when the kernel stops
98 kwargs['delete'] = False
99 kwargs['mode'] = 'w'
100 file = tempfile.NamedTemporaryFile(**kwargs)
101 self.files.append(file.name)
102 return file
103
104 def _write_to_stdout(self, contents):
105 self.send_response(self.iopub_socket, 'stream', {'name': 'stdout', 'text': contents})
106
107 def _write_to_stderr(self, contents):
108 self.send_response(self.iopub_socket, 'stream', {'name': 'stderr', 'text': contents})
109
110 def create_jupyter_subprocess(self, cmd):
111 return RealTimeSubprocess(cmd,
112 lambda contents: self._write_to_stdout(contents.decode()),
113 lambda contents: self._write_to_stderr(contents.decode()))
114
115 def compile_with_gcc(self, source_filename, binary_filename, cflags=None, ldflags=None):
116 cflags = ['-std=c11', '-fPIC', '-shared', '-rdynamic'] + cflags
117 args = ['gcc', source_filename] + cflags + ['-o', binary_filename] + ldflags
118 return self.create_jupyter_subprocess(args)
119
120 def _filter_magics(self, code):
121
122 magics = {'cflags': [],
123 'ldflags': [],
124 'args': []}
125
126 for line in code.splitlines():
127 if line.startswith('//%'):
128 key, value = line[3:].split(":", 2)
129 key = key.strip().lower()
130
131 if key in ['ldflags', 'cflags']:
132 for flag in value.split():
133 magics[key] += [flag]
134 elif key == "args":
135 # Split arguments respecting quotes
136 for argument in re.findall(r'(?:[^\s,"]|"(?:\\.|[^"])*")+', value):
137 magics['args'] += [argument.strip('"')]
138
139 return magics
140
141 def do_execute(self, code, silent, store_history=True,
142 user_expressions=None, allow_stdin=False):
143
144 magics = self._filter_magics(code)
145
146 with self.new_temp_file(suffix='.c') as source_file:
147 source_file.write(code)
148 source_file.flush()
149 with self.new_temp_file(suffix='.out') as binary_file:
150 p = self.compile_with_gcc(source_file.name, binary_file.name, magics['cflags'], magics['ldflags'])
151 while p.poll() is None:
152 p.write_contents()
153 p.write_contents()
154 if p.returncode != 0: # Compilation failed
155 self._write_to_stderr(
156 "[CbC kernel] GCC exited with code {}, the executable will not be executed".format(
157 p.returncode))
158 return {'status': 'ok', 'execution_count': self.execution_count, 'payload': [],
159 'user_expressions': {}}
160
161 p = self.create_jupyter_subprocess([self.master_path, binary_file.name] + magics['args'])
162 while p.poll() is None:
163 p.write_contents()
164 p.write_contents()
165
166 if p.returncode != 0:
167 self._write_to_stderr("[CbC kernel] Executable exited with code {}".format(p.returncode))
168 return {'status': 'ok', 'execution_count': self.execution_count, 'payload': [], 'user_expressions': {}}
169
170 def do_shutdown(self, restart):
171 """Cleanup the created source code files and executables when shutting down the kernel"""
172 self.cleanup_files()