Skip to content

Instantly share code, notes, and snippets.

@quasiben
Last active December 17, 2015 16:19
Show Gist options
  • Save quasiben/5637764 to your computer and use it in GitHub Desktop.
Save quasiben/5637764 to your computer and use it in GitHub Desktop.
# -*- coding: utf-8 -*-
"""C magic.
This is a very simple magic for compiling and importing C code with ctypes.
This derives from biteymagic.py, by Bradley Froehle:
https://gist.github.com/bfroehle/3458310
You must provide the function names you would like exported.
Example usage:
>>> %%c foo
int foo(int x)
{
return x*2;
}
>>> foo(4)
It doesn't do nearly as many interesting things as bitey, and could benefit
from integration with a parser such as:
https://bitbucket.org/eliben/pycparser
"""
#-----------------------------------------------------------------------------
# Copyright (C) 2013, Continuum Analytics
#
# Distributed under the terms of the Modified BSD License.
#-----------------------------------------------------------------------------
from __future__ import print_function
import ctypes
import imp
import io
import os
import pipes
import subprocess
import sys
from distutils.sysconfig import get_config_var, get_python_inc
try:
import hashlib
except ImportError:
import md5 as hashlib
from IPython.core.magic import (Magics, magics_class, line_magic,
cell_magic, line_cell_magic)
@magics_class
class CMagics(Magics):
"""Magics for C, a simple compile-and-import Tool"""
@cell_magic
def c(self, line, cell):
"""Compile single C function code and wrap/import using ctypes.
Usage, in cell mode::
%%c <function names to export>
<C code>
The user is responsible for using ctypes to handle function calls.
"""
code = cell if cell.endswith('\n') else cell+'\n'
fun_names = line.split()
lib_dir = os.path.join(self.shell.ipython_dir, 'cmagic')
key = line, code, sys.version_info, sys.executable
if not os.path.exists(lib_dir):
os.makedirs(lib_dir)
module_name = "_c_magic_" + \
hashlib.md5(str(key).encode('utf-8')).hexdigest()
c_name = module_name+'.c'
o_name = module_name+'.o'
# sorry Windows users :(, I don't know how to fix this default
lib_name = module_name + '.so'
c_path = os.path.join(lib_dir, c_name)
o_path = os.path.join(lib_dir, o_name)
lib_path = os.path.join(lib_dir, lib_name)
if not os.path.exists(c_path):
with io.open(c_path, 'w', encoding='utf-8') as f:
f.write(code)
if not os.path.exists(lib_path):
try:
startupinfo = None
if os.name == 'nt':
# Avoid a console window in Microsoft Windows.
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
cc = get_config_var('CC')
cflags = get_config_var('CFLAGS')
ccshared = get_config_var('CCSHARED')
ldshared = get_config_var('LDSHARED')
python_inc = get_python_inc()
cmd = cc.split() + ['-c', '-I', python_inc] + \
ccshared.split() + cflags.split() + [c_name]
subprocess.check_output(cmd,
stderr=subprocess.STDOUT,
cwd=lib_dir,
startupinfo=startupinfo)
cmd = ldshared.split() +['-o', lib_name, o_name]
subprocess.check_output(cmd,
stderr=subprocess.STDOUT,
cwd=lib_dir,
startupinfo=startupinfo)
except subprocess.CalledProcessError as e:
print(e.output, file=sys.stderr)
print("ERROR: command `%s` failed." %
' '.join(map(pipes.quote, e.cmd)),
file=sys.stderr)
return
lib = ctypes.CDLL(lib_path)
for fun_name in fun_names:
fun = getattr(lib, fun_name)
self.shell.push({fun_name:fun})
def load_ipython_extension(ip):
"""Load the extension in IPython."""
ip.register_magics(CMagics)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment