Mercurial > hg > Members > aka > jupyter_CbC_kernel
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() |