Skip to content
Snippets Groups Projects
Commit 0b43041d authored by Piotr Maślanka's avatar Piotr Maślanka
Browse files

v1.2

parents 539e6a71 d277f607
No related branches found
No related tags found
No related merge requests found
# v1.2
* fixed issue #1
# v1.1.2 # v1.1.2
* bugfix release: fixed behaviour if there was only * bugfix release: fixed behaviour if there was only
......
def times_seven(a):
return a*7
...@@ -10,6 +10,7 @@ cython_multibuilds = [ ...@@ -10,6 +10,7 @@ cython_multibuilds = [
# the reverse not necessarily being true (issue #5) # the reverse not necessarily being true (issue #5)
Multibuild('example_module', ['example_module/test.pyx', 'example_module/test2.pyx', Multibuild('example_module', ['example_module/test.pyx', 'example_module/test2.pyx',
'example_module/test3/test3.pyx', 'example_module/test3/test3.pyx',
'example_module/test3/test2.pyx',
'example_module/test_n.c']), 'example_module/test_n.c']),
Extension('example2.example', ['example2/example.pyx']), Extension('example2.example', ['example2/example.pyx']),
Multibuild('example3.example3.example3', ['example3/example3/example3/test.pyx']) Multibuild('example3.example3.example3', ['example3/example3/example3/test.pyx'])
......
from example_module.test import times_two from example_module.test import times_two
from example_module.test2 import times_three, times_five from example_module.test2 import times_three, times_five
from example_module.test3.test3 import times_four from example_module.test3.test3 import times_four
from example_module.test3.test2 import times_seven
from example2.example import test from example2.example import test
from example3.example3.example3.test import test as test_three from example3.example3.example3.test import test as test_three
import unittest import unittest
class TestExample(unittest.TestCase): class TestExample(unittest.TestCase):
def test_seven(self):
self.assertEqual(times_seven(4), 4*7)
def test_three(self): def test_three(self):
self.assertEqual(test_three(2, 3), 5) self.assertEqual(test_three(2, 3), 5)
......
from .build import build from .build import build
from .multibuild import Multibuild from .multibuild import Multibuild
__version__ = '1.1.2' __version__ = '1.2'
import logging
import typing as tp import typing as tp
from Cython.Build import cythonize from Cython.Build import cythonize
from setuptools import Extension from setuptools import Extension
...@@ -5,16 +6,24 @@ from .multibuild import Multibuild ...@@ -5,16 +6,24 @@ from .multibuild import Multibuild
MultiBuildType = tp.Union[Multibuild, Exception] MultiBuildType = tp.Union[Multibuild, Exception]
logger = logging.getLogger(__name__)
def build(extensions: tp.List[MultiBuildType], *args, **kwargs): def build(extensions: tp.List[MultiBuildType], *args, **kwargs):
returns = [] returns = []
multi_builds = []
for multi_build in extensions: for multi_build in extensions:
if isinstance(multi_build, Extension): if isinstance(multi_build, Extension):
returns.append(multi_build) returns.append(multi_build)
elif isinstance(multi_build, Multibuild): elif isinstance(multi_build, Multibuild):
multi_build.generate() multi_build.generate()
multi_builds.append(multi_build)
returns.append(multi_build.for_cythonize()) returns.append(multi_build.for_cythonize())
else: else:
raise ValueError('Invalid value in list, expected either an instance of Multibuild ' raise ValueError('Invalid value in list, expected either an instance of Multibuild '
'or an Extension') 'or an Extension')
return cythonize(returns, *args, **kwargs) values = cythonize(returns, *args, **kwargs)
for multi_build in multi_builds:
multi_build.do_after_cython()
logger.warning(multi_build.module_name_to_loader_function)
return values
\ No newline at end of file
import hashlib
import os import os
import collections import collections
import typing as tp import typing as tp
...@@ -7,18 +8,37 @@ from mako.template import Template ...@@ -7,18 +8,37 @@ from mako.template import Template
from setuptools import Extension from setuptools import Extension
CdefSection = collections.namedtuple('CdefSection', ('h_file_name', 'module_name')) CdefSection = collections.namedtuple('CdefSection', ('h_file_name', 'module_name', 'coded_module_name'))
GetDefinitionSection = collections.namedtuple('GetDefinitionSection', ( GetDefinitionSection = collections.namedtuple('GetDefinitionSection', (
'module_name', 'pyinit_name' 'module_name', 'pyinit_name', 'coded_module_name'
)) ))
def load_mako_lines(template_name: str) -> tp.List[str]:
return pkg_resources.resource_string('snakehouse', os.path.join('templates', template_name)).decode('utf8')
def cull_path(path):
if not path:
return path
if path[0] == os.path.sep:
if len(path) > 1:
if path[1] == os.path.sep:
return path[2:]
return path[1:]
else:
return path
def render_mako(template_name: str, **kwargs) -> str: def render_mako(template_name: str, **kwargs) -> str:
tpl = Template(pkg_resources.resource_string( tpl = Template(pkg_resources.resource_string(
'snakehouse', os.path.join('templates', template_name)).decode('utf8')) 'snakehouse', os.path.join('templates', template_name)).decode('utf8'))
return tpl.render(**kwargs) return tpl.render(**kwargs)
LINES_IN_HFILE = len(load_mako_lines('hfile.mako').split('\n'))
class Multibuild: class Multibuild:
""" """
This specifies a single Cython extension, called {extension_name}.__bootstrap__ This specifies a single Cython extension, called {extension_name}.__bootstrap__
...@@ -29,12 +49,9 @@ class Multibuild: ...@@ -29,12 +49,9 @@ class Multibuild:
:param files: list of pyx and c files :param files: list of pyx and c files
""" """
# sanitize path separators so that Linux-style paths are supported on Windows # sanitize path separators so that Linux-style paths are supported on Windows
files = list(files)
files = [os.path.join(*split(file)) for file in files] files = [os.path.join(*split(file)) for file in files]
self.files = list([file for file in files if not file.endswith('__bootstrap__.pyx')]) self.files = list([file for file in files if not file.endswith('__bootstrap__.pyx')])
file_name_set = set(os.path.split(file)[1] for file in self.files)
if len(self.files) != len(file_name_set):
raise ValueError('Two modules with the same name cannot appear together in a single '
'Multibuild')
self.pyx_files = [file for file in files if file.endswith('.pyx')] self.pyx_files = [file for file in files if file.endswith('.pyx')]
...@@ -43,55 +60,87 @@ class Multibuild: ...@@ -43,55 +60,87 @@ class Multibuild:
self.bootstrap_directory, _ = os.path.split(self.files[0]) # type: str self.bootstrap_directory, _ = os.path.split(self.files[0]) # type: str
else: else:
self.bootstrap_directory = os.path.commonpath(self.files) # type: str self.bootstrap_directory = os.path.commonpath(self.files) # type: str
self.modules = set() # type: tp.Set[tp.Tuple[str, str]] self.modules = [] # type: tp.List[tp.Tuple[str, str, str]]
self.module_name_to_loader_function = {}
for filename in self.pyx_files:
with open(filename, 'rb') as f_in:
self.module_name_to_loader_function[filename] = hashlib.sha256(f_in.read()).hexdigest()
def generate_header_files(self): def generate_header_files(self):
for filename in self.pyx_files: for filename in self.pyx_files:
path, name = os.path.split(filename) path, name, cmod_name_path, module_name, coded_module_name, complete_module_name = self.transform_module_name(filename)
if not name.endswith('.pyx'): if not name.endswith('.pyx'):
continue continue
module_name = name.replace('.pyx', '') h_file = filename.replace('.pyx', '.h')
h_name = name.replace('.pyx', '.h') if os.path.exists(h_file):
with open(h_file, 'r') as f_in:
data = f_in.readlines()
if os.path.exists(h_name): linesep = 'cr' if '\r\n' in data[0] else 'lf'
with open(os.path.join(path, h_name), 'r') as f_in:
data = f_in.read()
if 'PyObject* PyInit_' not in data: if not any('PyObject* PyInit_' in line for line in data):
data = render_mako('hfile.mako', initpy_name=module_name) + data data = [render_mako('hfile.mako', initpy_name=coded_module_name)+ \
'\r\n' if linesep == 'cr' else '\n'] + data[LINES_IN_HFILE:]
else: else:
data = render_mako('hfile.mako', initpy_name=module_name) data = render_mako('hfile.mako', initpy_name=coded_module_name)
with open(h_file, 'w') as f_out:
f_out.write(''.join(data))
with open(os.path.join(path, h_name), 'w') as f_out: def transform_module_name(self, filename):
path, name = os.path.split(filename)
module_name = name.replace('.pyx', '')
if path.startswith(self.bootstrap_directory):
cmod_name_path = path[len(self.bootstrap_directory):]
else:
cmod_name_path = path
path = cull_path(path)
cmod_name_path = cull_path(cmod_name_path)
if path:
intro = '.'.join((e for e in cmod_name_path.split(os.path.sep) if e))
if not intro:
complete_module_name = '%s.%s' % (self.extension_name, module_name)
else:
complete_module_name = '%s.%s.%s' % (self.extension_name,
intro,
module_name)
else:
complete_module_name = '%s.%s' % (self.extension_name, module_name)
coded_module_name = self.module_name_to_loader_function[filename]
return path, name, cmod_name_path, module_name, coded_module_name, complete_module_name
def do_after_cython(self):
for filename in self.pyx_files:
path, name, cmod_name_path, module_name, coded_module_name, complete_module_name = self.transform_module_name(filename)
to_replace = '__Pyx_PyMODINIT_FUNC PyInit_%s' % (module_name, )
replace_with = '__Pyx_PyMODINIT_FUNC PyInit_%s' % (coded_module_name, )
with open(filename.replace('.pyx', '.c'), 'r') as f_in:
data_in = f_in.read()
data = data_in.replace(to_replace, replace_with)
with open(filename.replace('.pyx', '.c'), 'w') as f_out:
f_out.write(data) f_out.write(data)
def generate_bootstrap(self) -> str: def generate_bootstrap(self) -> str:
cdef_section = [] cdef_section = []
for filename in self.pyx_files: for filename in self.pyx_files:
path, name = os.path.split(filename) path, name, cmod_name_path, module_name, coded_module_name, complete_module_name = self.transform_module_name(filename)
if path.startswith(self.bootstrap_directory):
path = path[len(self.bootstrap_directory):]
module_name = name.replace('.pyx', '')
if path:
h_path_name = os.path.join(path[1:], name.replace('.pyx', '.h')).\
replace('\\', '\\\\')
else:
h_path_name = name.replace('.pyx', '.h')
cdef_section.append(CdefSection(h_path_name, module_name))
if path: if os.path.exists(filename.replace('.pyx', '.c')):
complete_module_name = self.extension_name+'.'+'.'.join(path[1:].split( os.unlink(filename.replace('.pyx', '.c'))
os.path.sep))+'.'+module_name
else: h_path_name = os.path.join(cmod_name_path, name.replace('.pyx', '.h')).replace('\\', '\\\\')
complete_module_name = self.extension_name + '.'+module_name
self.modules.add((complete_module_name, module_name, )) cdef_section.append(CdefSection(h_path_name, module_name, coded_module_name))
self.modules.append((complete_module_name, module_name, coded_module_name))
get_definition = [] get_definition = []
for mod_name, init_fun_name in self.modules: for mod_name, init_fun_name, coded_module_name in self.modules:
get_definition.append(GetDefinitionSection(mod_name, init_fun_name)) get_definition.append(GetDefinitionSection(mod_name, init_fun_name, coded_module_name))
return render_mako('bootstrap.mako', cdef_sections=cdef_section, return render_mako('bootstrap.mako', cdef_sections=cdef_section,
get_definition_sections=get_definition, get_definition_sections=get_definition,
...@@ -121,7 +170,6 @@ class Multibuild: ...@@ -121,7 +170,6 @@ class Multibuild:
def for_cythonize(self, *args, **kwargs): def for_cythonize(self, *args, **kwargs):
for_cythonize = [*self.files, os.path.join(self.bootstrap_directory, '__bootstrap__.pyx')] for_cythonize = [*self.files, os.path.join(self.bootstrap_directory, '__bootstrap__.pyx')]
return Extension(self.extension_name+".__bootstrap__", return Extension(self.extension_name+".__bootstrap__",
for_cythonize, for_cythonize,
*args, *args,
......
import sys
cdef extern from "Python.h": cdef extern from "Python.h":
ctypedef struct PyModuleDef: ctypedef struct PyModuleDef:
const char* m_name; const char* m_name;
...@@ -8,21 +10,21 @@ cdef extern from "Python.h": ...@@ -8,21 +10,21 @@ cdef extern from "Python.h":
% for cdef_section in cdef_sections: % for cdef_section in cdef_sections:
cdef extern from "${cdef_section.h_file_name}": cdef extern from "${cdef_section.h_file_name}":
object PyInit_${cdef_section.module_name}() object PyInit_${cdef_section.coded_module_name}()
% endfor % endfor
cdef object get_definition_by_name(str name): cdef object get_definition_by_name(str name):
% for i, getdef_section in enumerate(get_definition_sections): % for i, getdef_section in enumerate(get_definition_sections):
% if i == 0: % if i == 0:
if name == "${getdef_section.module_name}": if name == "${getdef_section.module_name}":
return PyInit_${getdef_section.pyinit_name}() return PyInit_${getdef_section.coded_module_name}()
% else: % else:
elif name == "${getdef_section.module_name}": elif name == "${getdef_section.module_name}":
return PyInit_${getdef_section.pyinit_name}() return PyInit_${getdef_section.coded_module_name}()
% endif % endif
% endfor % endfor
import sys
cdef class CythonPackageLoader: cdef class CythonPackageLoader:
cdef PyModuleDef* definition cdef PyModuleDef* definition
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment