diff --git a/.codeclimate.yml b/.codeclimate.yml
index 54b94e29b5304291f5e660edc3cb38d3ddbfa2f7..b177cae40914bfb1f2138a22a63eca1e08abac80 100644
--- a/.codeclimate.yml
+++ b/.codeclimate.yml
@@ -1,19 +1,19 @@
-engines:
-  duplication:
-    enabled: true
-    config:
-      languages:
-        python:
-  fixme:
-    enabled: true
-  markdownlint:
-    enabled: true
-  pep8:
-    enabled: true
-  radon:
-    enabled: true
-exclude_paths:
-- example/**
-ratings:
-  paths:
-  - snakehouse/**
+engines:
+  duplication:
+    enabled: true
+    config:
+      languages:
+        python:
+  fixme:
+    enabled: true
+  markdownlint:
+    enabled: true
+  pep8:
+    enabled: true
+  radon:
+    enabled: true
+exclude_paths:
+- example/**
+ratings:
+  paths:
+  - snakehouse/**
diff --git a/.gitattributes b/.gitattributes
index 6ec0c5375dae1e104bd3c0a8db096cf3b7c17659..c096f8e1200b3df22b48ce56801cf3977ec45a6b 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,2 +1,3 @@
-*.mako text eol=lf
-*.sh text eol=lf
+*.mako text eol=lf
+*.sh text eol=lf
+snakehouse/mako.py text eol=lf
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index fb0e72f42c24e6993dbf227d3fcd0c4f0dcb918e..38d373526d8e02cc9087829b28fbbbf08f20e764 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,25 +1,25 @@
-name: CI
-run-name: ${{ github.actor }}
-on: [ push ]
-jobs:
-  tests:
-    runs-on: ubuntu-20.04
-    strategy:
-      matrix:
-        python-version: [ "3.5", "3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12" ]
-    steps:
-      - uses: actions/checkout@main
-      - uses: actions/setup-python@main
-        with:
-          python-version: ${{ matrix.python-version }}
-          cache: pip
-      - name: Install everything
-        run: |
-          pip install -U pip setuptools wheel disttools packaging pyproject.toml
-          python setup.py install
-      - name: Test
-        run: |
-          cd example
-          python setup.py test
-        env:
-          DEBUG: "1"
+name: CI
+run-name: ${{ github.actor }}
+on: [ push ]
+jobs:
+  tests:
+    runs-on: ubuntu-20.04
+    strategy:
+      matrix:
+        python-version: [ "3.5", "3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12" ]
+    steps:
+      - uses: actions/checkout@main
+      - uses: actions/setup-python@main
+        with:
+          python-version: ${{ matrix.python-version }}
+          cache: pip
+      - name: Install everything
+        run: |
+          pip install -U pip setuptools wheel disttools packaging pyproject.toml
+          python setup.py install
+      - name: Test
+        run: |
+          cd example
+          python setup.py test
+        env:
+          DEBUG: "1"
diff --git a/.gitignore b/.gitignore
index 8a87afaecc8e86c10e4a8235d55010ae531e6119..e06eb1a8ed003e0eace7990796b6648cff4b62af 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,16 +1,16 @@
-*.pyc
-*.pyd
-.idea/
-*.egg-info
-build
-*/build
-*/dist
-docs/_build
-dist
-.eggs
-*.c
-__bootstrap__.*
-venv/
-.venv/
-
-
+*.pyc
+*.pyd
+.idea/
+*.egg-info
+build
+*/build
+*/dist
+docs/_build
+dist
+.eggs
+*.c
+__bootstrap__.*
+venv/
+.venv/
+
+
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..41fc7eec093b08efe5e745b7d6fc430ff9048228
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,21 @@
+stages:
+  - test
+  - build
+
+
+.test:
+  stage: test
+  before_script:
+    - pip install --break-system-packages --upgrade Cython setuptools pip coverage nose2
+    - pip install --break-system-packages -r requirements.txt
+    - python setup.py build_ext --inplace
+    - python -m coverage run -m nose2 -vv -F
+    - python -m coverage report
+  variables:
+    TESTING: "1"
+  coverage: /TOTAL.*\s+(\d+\%)/
+
+
+test_python310:
+  extends: .test
+  image: python:3.10
\ No newline at end of file
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
index 3678d1a33ad62ee3f595fa6b6bf32bf81046ac71..93a38f6cb014da082b655510f483acb28629acc8 100644
--- a/.readthedocs.yaml
+++ b/.readthedocs.yaml
@@ -1,10 +1,10 @@
-version: 2
-build:
-  os: ubuntu-22.04
-  tools:
-    python: "3.9"
-python:
-  install:
-    - requirements: requirements.txt
-sphinx:
-  configuration: docs/conf.py
+version: 2
+build:
+  os: ubuntu-22.04
+  tools:
+    python: "3.9"
+python:
+  install:
+    - requirements: requirements.txt
+sphinx:
+  configuration: docs/conf.py
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d12692eedd0f9f771bd8841bd35fd40a8c3ddbd4..44408fd94749ba424dd645ccb98bc2dafeb29644 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,77 +1,77 @@
-# v1.7
-
-* should work on Windows now
-
-# v1.6
-
-* certified for Python 3.11 and 3.12
-* CI moved to GitHub Actions
-* downgraded Mako, since newer Mako requires Python 3.8
-
-# v1.5
-
-* fixed logging
-* `snakehouse` doesn't need cython and satella installed in advance
-* added `find_all`
-* made available for PyPy users
-* deprecated `find_pyx`, `find_c` and `find_pyx_and_c`
-
-# v1.4
-
-* added `find_pyx`, `find_c` and `find_pyx_and_c`
-* added documentation
-
-# v1.3.2
-
-* added `read_requirements_txt`
-
-# v1.3.1
-
-* Cython build step ("cythonize") will be parallelized by default
-
-# v1.3
-
-* added an option to build every file as a separate extension, for
-  usage in tests
-
-# v1.2.3
-
-* `Multibuild` will pass given kwargs to `Extension` object
-* added an option to monkey-patch `distutils` to compile multicore
-
-# v1.2.2
-
-* snakehouse will pass the remaining arguments to Multibuild to Cython's Extension
-
-# v1.2.1
-
-* snakehouse won't complain anymore if installing 
-  from a source wheel
-
-# v1.2
-
-* fixed issue #1
-
-# v1.1.2
-
-* bugfix release: fixed behaviour if there was only
-  a single file in Multibuild
-
-# v1.1.1
-
-* allowed Linux-style paths on Windows build environments
-
-# v1.1
-
-* added the capability to insert standard `Extension`s
-  in the snakehouse build() command
-
-# v1.0.2
-
-* got rid of some C compiler warnings
-* module will now use mako to render the files
-
-# v1.0.1
-
-* standard C files will be allowed in the builds
-* added support for Pythons 3.5-3.6
+# v1.7
+
+* should work on Windows now
+
+# v1.6
+
+* certified for Python 3.11 and 3.12
+* CI moved to GitHub Actions
+* downgraded Mako, since newer Mako requires Python 3.8
+
+# v1.5
+
+* fixed logging
+* `snakehouse` doesn't need cython and satella installed in advance
+* added `find_all`
+* made available for PyPy users
+* deprecated `find_pyx`, `find_c` and `find_pyx_and_c`
+
+# v1.4
+
+* added `find_pyx`, `find_c` and `find_pyx_and_c`
+* added documentation
+
+# v1.3.2
+
+* added `read_requirements_txt`
+
+# v1.3.1
+
+* Cython build step ("cythonize") will be parallelized by default
+
+# v1.3
+
+* added an option to build every file as a separate extension, for
+  usage in tests
+
+# v1.2.3
+
+* `Multibuild` will pass given kwargs to `Extension` object
+* added an option to monkey-patch `distutils` to compile multicore
+
+# v1.2.2
+
+* snakehouse will pass the remaining arguments to Multibuild to Cython's Extension
+
+# v1.2.1
+
+* snakehouse won't complain anymore if installing 
+  from a source wheel
+
+# v1.2
+
+* fixed issue #1
+
+# v1.1.2
+
+* bugfix release: fixed behaviour if there was only
+  a single file in Multibuild
+
+# v1.1.1
+
+* allowed Linux-style paths on Windows build environments
+
+# v1.1
+
+* added the capability to insert standard `Extension`s
+  in the snakehouse build() command
+
+# v1.0.2
+
+* got rid of some C compiler warnings
+* module will now use mako to render the files
+
+# v1.0.1
+
+* standard C files will be allowed in the builds
+* added support for Pythons 3.5-3.6
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 08144455d25d5e2e159c73eecb952f19ea9b3b7e..7525a7f851505164fdc5c883ef3b6bdf6eaf80ae 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -1,4 +1,4 @@
-List of contributors
-====================
-
-Piotr Maślanka <pmaslanka at smok dot co>
+List of contributors
+====================
+
+Piotr Maślanka <pmaslanka at smok dot co>
diff --git a/LICENSE b/LICENSE
index e6624836b5fc69344b8b2a63e26436a4280802c5..a8cbfadc2dcd3c5715406263a6a8624e78e5f880 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,21 +1,21 @@
-MIT License
-
-Copyright (c) 2020-2024 SMOK sp. z o. o.
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+MIT License
+
+Copyright (c) 2020-2024 SMOK sp. z o. o.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/MANIFEST.in b/MANIFEST.in
index 1aafccb6ddb8ba349adeafbd0655602a5d7ae8b0..fae74cbef848f98af068068a6563691294b63e09 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,5 +1,5 @@
-include LICENSE
-include README.md
-include CONTRIBUTORS.md
-include snakehouse/templates/*.mako
-include requirements.txt
+include LICENSE
+include README.md
+include CONTRIBUTORS.md
+include snakehouse/templates/*.mako
+include requirements.txt
diff --git a/README.md b/README.md
index 03bb535dc2c0a0091cede6d421e22188acd5e90b..d04c92ff05af4294b082e6b16eef738eabfbee62 100644
--- a/README.md
+++ b/README.md
@@ -1,37 +1,37 @@
-snakehouse
-==========
-![Build status](https://github.com/smok-serwis/snakehouse/actions/workflows/ci.yml/badge.svg)
-[![Code Climate](https://codeclimate.com/github/smok-serwis/snakehouse/badges/gpa.svg)](https://codeclimate.com/github/smok-serwis/snakehouse)
-[![Issue Count](https://codeclimate.com/github/smok-serwis/snakehouse/badges/issue_count.svg)](https://codeclimate.com/github/smok-serwis/snakehouse)
-[![PyPI](https://img.shields.io/pypi/pyversions/snakehouse.svg)](https://pypi.python.org/pypi/snakehouse)
-[![PyPI version](https://badge.fury.io/py/snakehouse.svg)](https://badge.fury.io/py/snakehouse)
-[![PyPI](https://img.shields.io/pypi/implementation/snakehouse.svg)](https://pypi.python.org/pypi/snakehouse)
-[![PyPI](https://img.shields.io/pypi/wheel/snakehouse.svg)]()
-[![Documentation Status](https://readthedocs.org/projects/snakehouse/badge/?version=latest)](http://snakehouse.readthedocs.io/en/latest/?badge=latest)
-[![License](https://img.shields.io/pypi/l/snakehouse)](https://github.com/smok-serwis/snakehouse)
-
-**IMPORTANT!**
-
-Since for now we've lost our PyPI access, please install the packages in a following way:
-
-```
-pip install git+https://github.com/smok-serwis/snakehouse.git
-```
-
-snakehouse is a tool to pack mutiple .pyx files
-into a single extension so that they are importable as separate
-Python modules inside Python.
-
-Inspired by [this StackOverflow discussion](https://stackoverflow.com/questions/30157363/collapse-multiple-submodules-to-one-cython-extension).
-
-Tested and works on CPython 3.5-3.12, 
-both Windows and [Linux](https://travis-ci.org/github/smok-serwis/snakehouse).
-
-It doesn't work on PyPy due to lack of
-`PyModule_FromDefAndSpec` symbol.
-
-READ BEFORE YOU USE
-===================
-
-Be sure to read the [docs](http://snakehouse.readthedocs.io/en/latest/) 
-before you start using it.
+snakehouse
+==========
+![Build status](https://github.com/smok-serwis/snakehouse/actions/workflows/ci.yml/badge.svg)
+[![Code Climate](https://codeclimate.com/github/smok-serwis/snakehouse/badges/gpa.svg)](https://codeclimate.com/github/smok-serwis/snakehouse)
+[![Issue Count](https://codeclimate.com/github/smok-serwis/snakehouse/badges/issue_count.svg)](https://codeclimate.com/github/smok-serwis/snakehouse)
+[![PyPI](https://img.shields.io/pypi/pyversions/snakehouse.svg)](https://pypi.python.org/pypi/snakehouse)
+[![PyPI version](https://badge.fury.io/py/snakehouse.svg)](https://badge.fury.io/py/snakehouse)
+[![PyPI](https://img.shields.io/pypi/implementation/snakehouse.svg)](https://pypi.python.org/pypi/snakehouse)
+[![PyPI](https://img.shields.io/pypi/wheel/snakehouse.svg)]()
+[![Documentation Status](https://readthedocs.org/projects/snakehouse/badge/?version=latest)](http://snakehouse.readthedocs.io/en/latest/?badge=latest)
+[![License](https://img.shields.io/pypi/l/snakehouse)](https://github.com/smok-serwis/snakehouse)
+
+**IMPORTANT!**
+
+Since for now we've lost our PyPI access, please install the packages in a following way:
+
+```
+pip install git+https://github.com/smok-serwis/snakehouse.git
+```
+
+snakehouse is a tool to pack mutiple .pyx files
+into a single extension so that they are importable as separate
+Python modules inside Python.
+
+Inspired by [this StackOverflow discussion](https://stackoverflow.com/questions/30157363/collapse-multiple-submodules-to-one-cython-extension).
+
+Tested and works on CPython 3.5-3.12, 
+both Windows and [Linux](https://travis-ci.org/github/smok-serwis/snakehouse).
+
+It doesn't work on PyPy due to lack of
+`PyModule_FromDefAndSpec` symbol.
+
+READ BEFORE YOU USE
+===================
+
+Be sure to read the [docs](http://snakehouse.readthedocs.io/en/latest/) 
+before you start using it.
diff --git a/docs/Makefile b/docs/Makefile
index d4bb2cbb9eddb1bb1b4f366623044af8e4830919..73a28c7134cd1760744f34bac4ebdedfbed40f72 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -1,20 +1,20 @@
-# Minimal makefile for Sphinx documentation
-#
-
-# You can set these variables from the command line, and also
-# from the environment for the first two.
-SPHINXOPTS    ?=
-SPHINXBUILD   ?= sphinx-build
-SOURCEDIR     = .
-BUILDDIR      = _build
-
-# Put it first so that "make" without argument is like "make help".
-help:
-	@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
-
-.PHONY: help Makefile
-
-# Catch-all target: route all unknown targets to Sphinx using the new
-# "make mode" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).
-%: Makefile
-	@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line, and also
+# from the environment for the first two.
+SPHINXOPTS    ?=
+SPHINXBUILD   ?= sphinx-build
+SOURCEDIR     = .
+BUILDDIR      = _build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+	@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+	@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
diff --git a/docs/accelerating.rst b/docs/accelerating.rst
index 754946ae7fd5982a5c1b745e57fa8810d483389f..2e5539fa48260014b50dcb6bb6797e86267c6750 100644
--- a/docs/accelerating.rst
+++ b/docs/accelerating.rst
@@ -1,17 +1,17 @@
-Accelerating builds
-===================
-
-distutils by default compiles using a single process.
-To enable faster, multiprocess compilations just use:
-
-.. code-block:: python
-
-    from snakehouse import monkey_patch_parallel_compilation
-
-    monkey_patch_parallel_compilation()
-
-In your :code:`setup.py` before your call to :code:`setup()`.
-
-It is also used in example_ so you can just copy that.
-
-.. _example: https://github.com/smok-serwis/snakehouse/blob/develop/example/setup.py
+Accelerating builds
+===================
+
+distutils by default compiles using a single process.
+To enable faster, multiprocess compilations just use:
+
+.. code-block:: python
+
+    from snakehouse import monkey_patch_parallel_compilation
+
+    monkey_patch_parallel_compilation()
+
+In your :code:`setup.py` before your call to :code:`setup()`.
+
+It is also used in example_ so you can just copy that.
+
+.. _example: https://github.com/smok-serwis/snakehouse/blob/develop/example/setup.py
diff --git a/docs/conf.py b/docs/conf.py
index 0ea113419691b0e2059d0f56634ba60b87ea9b14..394969bf92bc5070831828f2dd99a05640b78061 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -1,55 +1,55 @@
-# Configuration file for the Sphinx documentation builder.
-#
-# This file only contains a selection of the most common options. For a full
-# list see the documentation:
-# https://www.sphinx-doc.org/en/master/usage/configuration.html
-
-# -- Path setup --------------------------------------------------------------
-
-# If extensions (or modules to document with autodoc) are in another directory,
-# add these directories to sys.path here. If the directory is relative to the
-# documentation root, use os.path.abspath to make it absolute, like shown here.
-#
-import os
-import sys
-sys.path.insert(0, os.path.abspath('..'))
-
-
-# -- Project information -----------------------------------------------------
-
-project = 'snakehouse'
-copyright = '2020-2021 SMOK sp. z o. o.'
-author = 'Piotr Maślanka'
-
-from snakehouse import __version__
-# The full version, including alpha/beta/rc tags
-release = __version__
-
-
-# -- General configuration ---------------------------------------------------
-
-# Add any Sphinx extension module names here, as strings. They can be
-# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
-# ones.
-extensions = ['sphinx.ext.autodoc']
-
-# Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
-
-# List of patterns, relative to source directory, that match files and
-# directories to ignore when looking for source files.
-# This pattern also affects html_static_path and html_extra_path.
-exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
-
-
-# -- Options for HTML output -------------------------------------------------
-
-# The theme to use for HTML and HTML Help pages.  See the documentation for
-# a list of builtin themes.
-#
-html_theme = 'alabaster'
-
-# Add any paths that contain custom static files (such as style sheets) here,
-# relative to this directory. They are copied after the builtin static files,
-# so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['_static']
+# Configuration file for the Sphinx documentation builder.
+#
+# This file only contains a selection of the most common options. For a full
+# list see the documentation:
+# https://www.sphinx-doc.org/en/master/usage/configuration.html
+
+# -- Path setup --------------------------------------------------------------
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#
+import os
+import sys
+sys.path.insert(0, os.path.abspath('..'))
+
+
+# -- Project information -----------------------------------------------------
+
+project = 'snakehouse'
+copyright = '2020-2021 SMOK sp. z o. o.'
+author = 'Piotr Maślanka'
+
+from snakehouse import __version__
+# The full version, including alpha/beta/rc tags
+release = __version__
+
+
+# -- General configuration ---------------------------------------------------
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = ['sphinx.ext.autodoc']
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This pattern also affects html_static_path and html_extra_path.
+exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
+
+
+# -- Options for HTML output -------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+#
+html_theme = 'alabaster'
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
diff --git a/docs/coverage.rst b/docs/coverage.rst
index 120dca27fb91771095ed5bc2990804f9e22a6f3f..b2acef3f0893d842568b95ef34a32d8832202b40 100644
--- a/docs/coverage.rst
+++ b/docs/coverage.rst
@@ -1,7 +1,7 @@
-Coverage
-========
-
-:code:`snakehouse` is fully compatible with coverage. Go see how it's done
-in tempsdb_.
-
-.. _tempsdb: https://github.com/smok-serwis/tempsdb
+Coverage
+========
+
+:code:`snakehouse` is fully compatible with coverage. Go see how it's done
+in tempsdb_.
+
+.. _tempsdb: https://github.com/smok-serwis/tempsdb
diff --git a/docs/index.rst b/docs/index.rst
index b0789102e7f493abc1942fbe70c28af2d0be0aef..d169b9e2ef96b906983b6942b2ff5f2d32a44b18 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -1,106 +1,106 @@
-Welcome to snakehouse's documentation!
-======================================
-
-.. toctree::
-   :maxdepth: 2
-   :caption: Contents:
-
-   usage
-   utilities
-   coverage
-   accelerating
-
-What is snakehouse?
-===================
-
-Snakehouse_ is a package that helps_ you put multiple :code:`.pyx` files in a single
-Python :code:`so`/:code:`DLL`, so that each is importable by Python as if they
-were just plain :code:`.py` files.
-
-.. _helps: https://stackoverflow.com/questions/30157363/collapse-multiple-submodules-to-one-cython-extension
-
-.. _Snakehouse: https://github.com/smok-serwis/snakehouse
-
-How do I install it?
---------------------
-
-Do it via
-
-.. code-block:: bash
-
-    pip install snakehouse
-
-All dependencies will be installed automatically.
-
-Mandatory reading and limitations
-=================================
-
-Take a look at example_ on how to multi-build your Cython extensions.
-
-.. _example: https://github.com/smok-serwis/snakehouse/blob/develop/example/setup.py
-
-Don't place modules compiled that way in root .py file's top level imports.
-Wrap them in a layer of indirection instead!
-
-So if your module is called :code:`example`, make a :code:`start_example/__main__.py` with
-the following code:
-
-.. code-block:: python
-
-    if __name__ == '__main__':
-        from example import run
-        run()
-
-Or however you do start your application.
-
-This applies to unit tests as well!
-
-When something goes wrong (eg. the application throws an unhandled exception)
-the built module has a tendency to dump core.
-Try to debug it first by passing :code:`dont_snakehouse=True` to your
-modules in the debug mode.
-
-Also note that if you are compiling in :code:`dont_snakehouse`
-mode then your modules should have at least one of the following:
-
-* a normal Python :code:`def`
-* a normal Python class (not :code:`cdef class`)
-* a line of Python initialization, eg.
-
-.. code-block:: python
-
-   a = None
-
-or
-
-.. code-block:: python
-
-   import logging
-
-   logger = logging.getLogger(__name__)
-
-Otherwise :code:`PyInit` won't be generated by Cython
-and such module will be unimportable in Python. Normal import won't suffice.
-
-Please install Snakehouse in a separate venv. This is because it requires ancient version of
-several packages, because authors of these packages were quick to drop support for
-earlier Pythons.
-
-Contributions
-=============
-
-Contributions are most welcome. Just add yourself to :code:`CONTRIBUTORS.md` list
-at your pull request. At this moment most pressing issues are the segfaulting problem,
-where snakehouse-built libraries segfault_ the Python interpreter when there's an unhandled
-exception (sometimes, I can't really seem to pinpoint the problem source).
-
-Try to unit test what you're changing, but that is by no way a requirement.
-
-.. _segfault: https://github.com/smok-serwis/snakehouse/issues/7
-
-Indices and tables
-==================
-
-* :ref:`genindex`
-* :ref:`modindex`
-* :ref:`search`
+Welcome to snakehouse's documentation!
+======================================
+
+.. toctree::
+   :maxdepth: 2
+   :caption: Contents:
+
+   usage
+   utilities
+   coverage
+   accelerating
+
+What is snakehouse?
+===================
+
+Snakehouse_ is a package that helps_ you put multiple :code:`.pyx` files in a single
+Python :code:`so`/:code:`DLL`, so that each is importable by Python as if they
+were just plain :code:`.py` files.
+
+.. _helps: https://stackoverflow.com/questions/30157363/collapse-multiple-submodules-to-one-cython-extension
+
+.. _Snakehouse: https://github.com/smok-serwis/snakehouse
+
+How do I install it?
+--------------------
+
+Do it via
+
+.. code-block:: bash
+
+    pip install snakehouse
+
+All dependencies will be installed automatically.
+
+Mandatory reading and limitations
+=================================
+
+Take a look at example_ on how to multi-build your Cython extensions.
+
+.. _example: https://github.com/smok-serwis/snakehouse/blob/develop/example/setup.py
+
+Don't place modules compiled that way in root .py file's top level imports.
+Wrap them in a layer of indirection instead!
+
+So if your module is called :code:`example`, make a :code:`start_example/__main__.py` with
+the following code:
+
+.. code-block:: python
+
+    if __name__ == '__main__':
+        from example import run
+        run()
+
+Or however you do start your application.
+
+This applies to unit tests as well!
+
+When something goes wrong (eg. the application throws an unhandled exception)
+the built module has a tendency to dump core.
+Try to debug it first by passing :code:`dont_snakehouse=True` to your
+modules in the debug mode.
+
+Also note that if you are compiling in :code:`dont_snakehouse`
+mode then your modules should have at least one of the following:
+
+* a normal Python :code:`def`
+* a normal Python class (not :code:`cdef class`)
+* a line of Python initialization, eg.
+
+.. code-block:: python
+
+   a = None
+
+or
+
+.. code-block:: python
+
+   import logging
+
+   logger = logging.getLogger(__name__)
+
+Otherwise :code:`PyInit` won't be generated by Cython
+and such module will be unimportable in Python. Normal import won't suffice.
+
+Please install Snakehouse in a separate venv. This is because it requires ancient version of
+several packages, because authors of these packages were quick to drop support for
+earlier Pythons.
+
+Contributions
+=============
+
+Contributions are most welcome. Just add yourself to :code:`CONTRIBUTORS.md` list
+at your pull request. At this moment most pressing issues are the segfaulting problem,
+where snakehouse-built libraries segfault_ the Python interpreter when there's an unhandled
+exception (sometimes, I can't really seem to pinpoint the problem source).
+
+Try to unit test what you're changing, but that is by no way a requirement.
+
+.. _segfault: https://github.com/smok-serwis/snakehouse/issues/7
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
diff --git a/docs/make.bat b/docs/make.bat
index 922152e96a04a242e6fc40f124261d74890617d8..2119f51099bf37e4fdb6071dce9f451ea44c62dd 100644
--- a/docs/make.bat
+++ b/docs/make.bat
@@ -1,35 +1,35 @@
-@ECHO OFF
-
-pushd %~dp0
-
-REM Command file for Sphinx documentation
-
-if "%SPHINXBUILD%" == "" (
-	set SPHINXBUILD=sphinx-build
-)
-set SOURCEDIR=.
-set BUILDDIR=_build
-
-if "%1" == "" goto help
-
-%SPHINXBUILD% >NUL 2>NUL
-if errorlevel 9009 (
-	echo.
-	echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
-	echo.installed, then set the SPHINXBUILD environment variable to point
-	echo.to the full path of the 'sphinx-build' executable. Alternatively you
-	echo.may add the Sphinx directory to PATH.
-	echo.
-	echo.If you don't have Sphinx installed, grab it from
-	echo.http://sphinx-doc.org/
-	exit /b 1
-)
-
-%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
-goto end
-
-:help
-%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
-
-:end
-popd
+@ECHO OFF
+
+pushd %~dp0
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+	set SPHINXBUILD=sphinx-build
+)
+set SOURCEDIR=.
+set BUILDDIR=_build
+
+if "%1" == "" goto help
+
+%SPHINXBUILD% >NUL 2>NUL
+if errorlevel 9009 (
+	echo.
+	echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
+	echo.installed, then set the SPHINXBUILD environment variable to point
+	echo.to the full path of the 'sphinx-build' executable. Alternatively you
+	echo.may add the Sphinx directory to PATH.
+	echo.
+	echo.If you don't have Sphinx installed, grab it from
+	echo.http://sphinx-doc.org/
+	exit /b 1
+)
+
+%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+goto end
+
+:help
+%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+
+:end
+popd
diff --git a/docs/usage.rst b/docs/usage.rst
index 7b58d170d336d53e4bbe890019b369b5343f562f..605fa282c75293991396d3a137eeb08ae069ac46 100644
--- a/docs/usage.rst
+++ b/docs/usage.rst
@@ -1,52 +1,52 @@
-Usage
-=====
-
-To use snakehouse just use the following in your :code:`setup.py`:
-
-.. code-block:: python
-
-    from snakehouse import Multibuild, build
-
-    extensions = build([
-                    Multibuild('example_module', list_of_pyx_files)
-                    ], compiler_directives={
-                       'language_level': '3',
-                    })
-
-    setup(name='example_module',
-      version='0.1',
-      packages=['example_module'],
-      ext_modules=extensions
-    )
-
-You can pass also :code:`setuptools`'s :code:`Extensions` objects, as detailed in
-example_.
-
-.. _example: https://github.com/smok-serwis/snakehouse/blob/develop/example/setup.py
-
-Full pydoc of :code:`Multibuild` and :code:`build` is here
-
-.. autoclass:: snakehouse.Multibuild
-    :members:
-
-.. autofunction:: snakehouse.build
-
-You should use :code:`dont_snakehouse` for debugging and unit tests, as
-snakehouse has a sad tendency to dump core on unhandled exceptions. To prevent that
-from happening remember to handle your exceptions and debug using this flag.
-
-If you need to locate all .pyx files in a certain directory, you can do the following:
-
-.. code-block:: python
-
-    from snakehouse import Multibuild, build, find_all
-
-    extensions = build([
-                    Multibuild('example_module', find_all('src'))
-                    ], compiler_directives={
-                       'language_level': '3',
-                    })
-
-The documentation to :class:`~snakehouse.find_all` is as follows:
-
-.. autoclass:: snakehouse.find_all
+Usage
+=====
+
+To use snakehouse just use the following in your :code:`setup.py`:
+
+.. code-block:: python
+
+    from snakehouse import Multibuild, build
+
+    extensions = build([
+                    Multibuild('example_module', list_of_pyx_files)
+                    ], compiler_directives={
+                       'language_level': '3',
+                    })
+
+    setup(name='example_module',
+      version='0.1',
+      packages=['example_module'],
+      ext_modules=extensions
+    )
+
+You can pass also :code:`setuptools`'s :code:`Extensions` objects, as detailed in
+example_.
+
+.. _example: https://github.com/smok-serwis/snakehouse/blob/develop/example/setup.py
+
+Full pydoc of :code:`Multibuild` and :code:`build` is here
+
+.. autoclass:: snakehouse.Multibuild
+    :members:
+
+.. autofunction:: snakehouse.build
+
+You should use :code:`dont_snakehouse` for debugging and unit tests, as
+snakehouse has a sad tendency to dump core on unhandled exceptions. To prevent that
+from happening remember to handle your exceptions and debug using this flag.
+
+If you need to locate all .pyx files in a certain directory, you can do the following:
+
+.. code-block:: python
+
+    from snakehouse import Multibuild, build, find_all
+
+    extensions = build([
+                    Multibuild('example_module', find_all('src'))
+                    ], compiler_directives={
+                       'language_level': '3',
+                    })
+
+The documentation to :class:`~snakehouse.find_all` is as follows:
+
+.. autoclass:: snakehouse.find_all
diff --git a/docs/utilities.rst b/docs/utilities.rst
index 740466266551d9e17dd364ed35d7350acae5bdc4..df44886a4b1d6fd6a403ef24e2936fa4762e8776 100644
--- a/docs/utilities.rst
+++ b/docs/utilities.rst
@@ -1,37 +1,37 @@
-Helper functions
-================
-
-Snakehouse contains a bunch of functions to help you with everyday work. They are
-meant to be primarily used in your :code:`setup.py`.
-
-Finding files
-~~~~~~~~~~~~~
-
-Instead of manually specifying list of pyx and c files to compile you can use the following
-functions:
-
-.. autofunction:: snakehouse.find_pyx
-
-.. autofunction:: snakehouse.find_c
-
-.. autofunction:: snakehouse.find_pyx_and_c
-
-Specifying requirements
-~~~~~~~~~~~~~~~~~~~~~~~
-
-If you add a MANIFEST.in file with contents:
-
-.. code-block::
-
-    include requirements.txt
-
-Then you can write the following in your setup.py:
-
-.. code-block:: python
-
-    from snakehouse import read_requirements_txt
-
-    setup(install_requires=read_requirements_txt())
-
-.. autofunction:: snakehouse.read_requirements_txt
-
+Helper functions
+================
+
+Snakehouse contains a bunch of functions to help you with everyday work. They are
+meant to be primarily used in your :code:`setup.py`.
+
+Finding files
+~~~~~~~~~~~~~
+
+Instead of manually specifying list of pyx and c files to compile you can use the following
+functions:
+
+.. autofunction:: snakehouse.find_pyx
+
+.. autofunction:: snakehouse.find_c
+
+.. autofunction:: snakehouse.find_pyx_and_c
+
+Specifying requirements
+~~~~~~~~~~~~~~~~~~~~~~~
+
+If you add a MANIFEST.in file with contents:
+
+.. code-block::
+
+    include requirements.txt
+
+Then you can write the following in your setup.py:
+
+.. code-block:: python
+
+    from snakehouse import read_requirements_txt
+
+    setup(install_requires=read_requirements_txt())
+
+.. autofunction:: snakehouse.read_requirements_txt
+
diff --git a/example/example2/__init__.py b/example/example2/__init__.py
index fa84e47ee529b348bfcf12c1c64433e3c127dde4..840b5a74012dd744cda5d19baa3f9f39d6ba5977 100644
--- a/example/example2/__init__.py
+++ b/example/example2/__init__.py
@@ -1,5 +1,5 @@
-import logging
-import typing as tp
-
-logger = logging.getLogger(__name__)
-
+import logging
+import typing as tp
+
+logger = logging.getLogger(__name__)
+
diff --git a/example/example2/example.pyx b/example/example2/example.pyx
index 4dcfb73e3bcb6adfd9e60c79597c93fd2bd57363..5bb8d6acf00da9b613a98ed2b4164343e4b40e35 100644
--- a/example/example2/example.pyx
+++ b/example/example2/example.pyx
@@ -1,2 +1,2 @@
-def test(x, y):
-    return x+y
+def test(x, y):
+    return x+y
diff --git a/example/example3/__init__.py b/example/example3/__init__.py
index fa84e47ee529b348bfcf12c1c64433e3c127dde4..840b5a74012dd744cda5d19baa3f9f39d6ba5977 100644
--- a/example/example3/__init__.py
+++ b/example/example3/__init__.py
@@ -1,5 +1,5 @@
-import logging
-import typing as tp
-
-logger = logging.getLogger(__name__)
-
+import logging
+import typing as tp
+
+logger = logging.getLogger(__name__)
+
diff --git a/example/example3/example3/__init__.py b/example/example3/example3/__init__.py
index fa84e47ee529b348bfcf12c1c64433e3c127dde4..840b5a74012dd744cda5d19baa3f9f39d6ba5977 100644
--- a/example/example3/example3/__init__.py
+++ b/example/example3/example3/__init__.py
@@ -1,5 +1,5 @@
-import logging
-import typing as tp
-
-logger = logging.getLogger(__name__)
-
+import logging
+import typing as tp
+
+logger = logging.getLogger(__name__)
+
diff --git a/example/example3/example3/example3/__init__.py b/example/example3/example3/example3/__init__.py
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..2e36ba8a3f8d0b688c825edf3b50d1f77a9f348d 100644
--- a/example/example3/example3/example3/__init__.py
+++ b/example/example3/example3/example3/__init__.py
@@ -0,0 +1,2 @@
+from example3.example3.example3.__bootstrap__ import bootstrap_cython_submodules
+bootstrap_cython_submodules()
diff --git a/example/example3/example3/example3/test.pyx b/example/example3/example3/example3/test.pyx
index c3169d453708e44aff9970b8f007e8b94924c4d1..6bf4d58aaf9caac81209d14bf7335b5941688177 100644
--- a/example/example3/example3/example3/test.pyx
+++ b/example/example3/example3/example3/test.pyx
@@ -1,2 +1,2 @@
-def test(a, b):
-    return a+b
+def test(a, b):
+    return a+b
diff --git a/example/example_module/__init__.py b/example/example_module/__init__.py
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e3a236add01b14f378ff1dcdcf057ec64e12d161 100644
--- a/example/example_module/__init__.py
+++ b/example/example_module/__init__.py
@@ -0,0 +1,2 @@
+from example_module.__bootstrap__ import bootstrap_cython_submodules
+bootstrap_cython_submodules()
diff --git a/example/example_module/test.pyx b/example/example_module/test.pyx
index d619e6d41164133b380059ccd2f977ceb50b40b3..05741ed0964123c7acf2be89c9afec1fc5425468 100644
--- a/example/example_module/test.pyx
+++ b/example/example_module/test.pyx
@@ -1,8 +1,8 @@
-import logging
-import typing as tp
-
-logger = logging.getLogger(__name__)
-
-def times_two(x):
-    return x*2
-
+import logging
+import typing as tp
+
+logger = logging.getLogger(__name__)
+
+def times_two(x):
+    return x*2
+
diff --git a/example/example_module/test2.pyx b/example/example_module/test2.pyx
index 29fcd98a06327644c064bd040def06b67dfb5c67..1fe0652d1892a9ccd1e176612fe3b231f48d6b2c 100644
--- a/example/example_module/test2.pyx
+++ b/example/example_module/test2.pyx
@@ -1,9 +1,9 @@
-cdef extern from "test_n.h":
-    float _times_five(float)
-
-
-def times_five(x):
-    return _times_five(x)
-
-def times_three(x):
-    return x*3
+cdef extern from "test_n.h":
+    float _times_five(float)
+
+
+def times_five(x):
+    return _times_five(x)
+
+def times_three(x):
+    return x*3
diff --git a/example/example_module/test3/__init__.py b/example/example_module/test3/__init__.py
index fa84e47ee529b348bfcf12c1c64433e3c127dde4..840b5a74012dd744cda5d19baa3f9f39d6ba5977 100644
--- a/example/example_module/test3/__init__.py
+++ b/example/example_module/test3/__init__.py
@@ -1,5 +1,5 @@
-import logging
-import typing as tp
-
-logger = logging.getLogger(__name__)
-
+import logging
+import typing as tp
+
+logger = logging.getLogger(__name__)
+
diff --git a/example/example_module/test3/test2.pyx b/example/example_module/test3/test2.pyx
index 503bfe88f7a8caba07b15a05393a9e45ed05e3bb..672fc329b1abc4f1c9aaac4d763f8970a511f4a1 100644
--- a/example/example_module/test3/test2.pyx
+++ b/example/example_module/test3/test2.pyx
@@ -1,2 +1,2 @@
-def times_seven(a):
-    return a*7
+def times_seven(a):
+    return a*7
diff --git a/example/example_module/test3/test3.pyx b/example/example_module/test3/test3.pyx
index 0d8accb9ca4f74e8aa170141d8cac94aa81c76d1..782e5d40950e9d4887016fe185689418256461b3 100644
--- a/example/example_module/test3/test3.pyx
+++ b/example/example_module/test3/test3.pyx
@@ -1,2 +1,2 @@
-def times_four(x):
-    return x*4
+def times_four(x):
+    return x*4
diff --git a/example/example_module/test_n.c b/example/example_module/test_n.c
index a511066d49471ba9d91b94fb54996e418680c89e..d493eb7759fd3641ca6b04123ebfa6c2a0e79c52 100644
--- a/example/example_module/test_n.c
+++ b/example/example_module/test_n.c
@@ -1,3 +1,3 @@
-float _times_five(float v) {
-    return v*5;
+float _times_five(float v) {
+    return v*5;
 }
\ No newline at end of file
diff --git a/example/example_module/test_n.h b/example/example_module/test_n.h
index b03c01152c0bbc326d64e2d8981a8baf561e7290..fe3cd7d7c76058deb397de275021a2d81b46533a 100644
--- a/example/example_module/test_n.h
+++ b/example/example_module/test_n.h
@@ -1 +1 @@
-float _times_five(float);
+float _times_five(float);
diff --git a/example/setup.py b/example/setup.py
index c123f29f7e92ac7ef7dedd372da2e7414c39af67..2e0723c88cb07f6add3992729a5835c6b4d8ce58 100644
--- a/example/setup.py
+++ b/example/setup.py
@@ -1,51 +1,47 @@
-import os
-
-from setuptools import setup
-
-from snakehouse import Multibuild, build, monkey_patch_parallel_compilation, find_pyx_and_c, \
-    find_all
-from setuptools import Extension
-
-monkey_patch_parallel_compilation()
-
-dont_snakehouse = False
-if 'DEBUG' in os.environ:
-    print('Debug is enabled!')
-    dont_snakehouse = True
-
-
-# note that you can include standard Extension classes in this list, those won't be touched
-# and will be directed directly to Cython.Build.cythonize()
-cython_multibuilds = [
-        # note that Windows-style pathes are supported on Linux build environment,
-        # the reverse not necessarily being true (issue #5)
-    Multibuild('example_module', find_all('example_module', True),
-               define_macros=[("CYTHON_TRACE_NOGIL", "1")],
-               dont_snakehouse=dont_snakehouse),
-    Extension('example2.example', ['example2/example.pyx']),
-    Multibuild('example3.example3.example3', ['example3/example3/example3/test.pyx'],
-               dont_snakehouse=dont_snakehouse)
-]
-
-# first argument is used directly by snakehouse, the rest and **kwargs are passed to
-# Cython.Build.cythonize()
-ext_modules = build(cython_multibuilds,
-                    compiler_directives={
-                       'language_level': '3',
-                    })
-
-setup(name='example_module',
-      version='0.1',
-      packages=['example_module', 'example2'],
-      install_requires=[
-            'Cython', 'snakehouse'
-      ],
-      zip_safe=False,
-      tests_require=[
-          "nose2"
-      ],
-      test_suite='nose2.collector.collector',
-      python_requires='!=2.7.*,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*',
-      ext_modules=ext_modules
-)
-
+import os
+
+from setuptools import setup
+
+from snakehouse import Multibuild, build, monkey_patch_parallel_compilation, find_pyx_and_c, \
+    find_all
+from setuptools import Extension
+
+monkey_patch_parallel_compilation()
+
+dont_snakehouse = False
+if 'DEBUG' in os.environ:
+    print('Debug is enabled!')
+    dont_snakehouse = True
+
+
+# note that you can include standard Extension classes in this list, those won't be touched
+# and will be directed directly to Cython.Build.cythonize()
+cython_multibuilds = [
+        # note that Windows-style pathes are supported on Linux build environment,
+        # the reverse not necessarily being true (issue #5)
+    Multibuild('example_module', find_all('example_module', True),
+               define_macros=[("CYTHON_TRACE_NOGIL", "1")],
+               dont_snakehouse=dont_snakehouse),
+    # Extension('example2.example', ['example2/example.pyx']),
+    # Multibuild('example3.example3.example3', ['example3/example3/example3/test.pyx'],
+    #            dont_snakehouse=dont_snakehouse)
+]
+
+# first argument is used directly by snakehouse, the rest and **kwargs are passed to
+# Cython.Build.cythonize()
+ext_modules = build(cython_multibuilds,
+                    compiler_directives={
+                       'language_level': '3',
+                    })
+
+setup(name='example_module',
+      version='0.1',
+      packages=['example_module', 'example2'],
+      install_requires=[
+            'Cython', 'snakehouse'
+      ],
+      zip_safe=False,
+      python_requires='!=2.7.*,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*',
+      ext_modules=ext_modules
+)
+
diff --git a/example/tests/__init__.py b/example/tests/__init__.py
index fa84e47ee529b348bfcf12c1c64433e3c127dde4..840b5a74012dd744cda5d19baa3f9f39d6ba5977 100644
--- a/example/tests/__init__.py
+++ b/example/tests/__init__.py
@@ -1,5 +1,5 @@
-import logging
-import typing as tp
-
-logger = logging.getLogger(__name__)
-
+import logging
+import typing as tp
+
+logger = logging.getLogger(__name__)
+
diff --git a/example/tests/test_test.py b/example/tests/test_test.py
index cb226877fed3ef07929d5dd2c9fee412e224e3fe..dd5181e598de75199c5c02c83eb3b7198058ac3b 100644
--- a/example/tests/test_test.py
+++ b/example/tests/test_test.py
@@ -1,30 +1,30 @@
-from example_module.test import times_two
-from example_module.test2 import times_three, times_five
-from example_module.test3.test3 import times_four
-from example_module.test3.test2 import times_seven
-from example2.example import test
-from example3.example3.example3.test import test as test_three
-import unittest
-
-
-class TestExample(unittest.TestCase):
-    def test_seven(self):
-        self.assertEqual(times_seven(4), 4*7)
-
-    def test_three(self):
-        self.assertEqual(test_three(2, 3), 5)
-
-    def test_test(self):
-        self.assertEqual(test(2, 3), 5)
-
-    def test_five(self):
-        self.assertEqual(times_five(2), 10)
-
-    def test_two(self):
-        self.assertEqual(times_two(2), 4)
-
-    def test_three(self):
-        self.assertEqual(times_three(2), 6)
-
-    def test_four(self):
-        self.assertEqual(times_four(2), 8)
+from example_module.test import times_two
+from example_module.test2 import times_three, times_five
+from example_module.test3.test3 import times_four
+from example_module.test3.test2 import times_seven
+from example2.example import test
+from example3.example3.example3.test import test as test_three
+import unittest
+
+
+class TestExample(unittest.TestCase):
+    def test_seven(self):
+        self.assertEqual(times_seven(4), 4*7)
+
+    def test_three(self):
+        self.assertEqual(test_three(2, 3), 5)
+
+    def test_test(self):
+        self.assertEqual(test(2, 3), 5)
+
+    def test_five(self):
+        self.assertEqual(times_five(2), 10)
+
+    def test_two(self):
+        self.assertEqual(times_two(2), 4)
+
+    def test_three(self):
+        self.assertEqual(times_three(2), 6)
+
+    def test_four(self):
+        self.assertEqual(times_four(2), 8)
diff --git a/requirements.txt b/requirements.txt
index dcb94b4e453994b2ede1a5d1815bf587d7aa317d..4700652c0eaa5ed4a31a3439b24aaf55b9e1aeff 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,4 @@
-Cython
-mako==1.1.0
-satella==2.20.0
-MarkupSafe==1.1.1
+Cython
+mako==1.1.0
+satella==2.20.0
+MarkupSafe==1.1.1
diff --git a/setup.cfg b/setup.cfg
index 00677200a89039df3be2c41b665bcf8d45663880..8b7cea7daa005bc4aa4f515deb32c8fd429b34ea 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,60 +1,57 @@
-# coding: utf-8
-[metadata]
-name = snakehouse
-keywords = cython, extension, multiple, pyx
-version = 1.7
-long-description = file: README.md
-long-description-content-type = text/markdown; charset=UTF-8
-license_files = LICENSE
-author = Piotr Maślanka
-author_email = pmaslanka@smok.co
-description = Utilities for packing multiple pyx files into a single Cython extension
-url = https://github.com/smok-serwis/snakehouse
-project-urls =
-    Code = https://github.com/smok-serwis/snakehouse
-    Issue tracker = https://github.com/smok-serwis/snakehouse/issues
-classifier =
-    Programming Language :: Python
-    Programming Language :: Python :: 3.5
-    Programming Language :: Python :: 3.6
-    Programming Language :: Python :: 3.7
-    Programming Language :: Python :: 3.8
-    Programming Language :: Python :: 3.9
-    Programming Language :: Python :: 3.10
-    Programming Language :: Python :: 3.11
-    Programming Language :: Python :: 3.12
-    Programming Language :: Python :: Implementation :: CPython
-    Operating System :: OS Independent
-    Development Status :: 5 - Production/Stable
-    License :: OSI Approved :: MIT License
-    Topic :: Software Development :: Code Generators
-    Topic :: Software Development :: Build Tools
-
-[options]
-install_requires =
-    Cython
-    mako == 1.1.0
-    satella == 2.20.0
-    MarkupSafe == 1.1.1
-
-python_requires = !=2.7.*,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*
-packages = find:
-
-
-[options.packages.find]
-exclude =
-    docs
-    example
-
-[options.package_data]
-snakehouse = templates/*.mako
-
-[pycodestyle]
-max-line-length = 100
-
-[pep8]
-max-line-length = 100
-
-[bdist_wheel]
-universal = 0
-
+# coding: utf-8
+[metadata]
+name = snakehouse
+keywords = cython, extension, multiple, pyx
+version = 1.7
+long_description = file: README.md
+long_description-content-type = text/markdown; charset=UTF-8
+license_files = LICENSE
+author = Piotr Maślanka
+author_email = pmaslanka@smok.co
+description = Utilities for packing multiple pyx files into a single Cython extension
+url = https://github.com/smok-serwis/snakehouse
+project_urls =
+    Code = https://github.com/smok-serwis/snakehouse
+    Issue tracker = https://github.com/smok-serwis/snakehouse/issues
+classifier =
+    Programming Language :: Python
+    Programming Language :: Python :: 3.5
+    Programming Language :: Python :: 3.6
+    Programming Language :: Python :: 3.7
+    Programming Language :: Python :: 3.8
+    Programming Language :: Python :: 3.9
+    Programming Language :: Python :: 3.10
+    Programming Language :: Python :: 3.11
+    Programming Language :: Python :: 3.12
+    Programming Language :: Python :: Implementation :: CPython
+    Operating System :: OS Independent
+    Development Status :: 5 - Production/Stable
+    License :: OSI Approved :: MIT License
+    Topic :: Software Development :: Code Generators
+    Topic :: Software Development :: Build Tools
+
+[options]
+install_requires =
+    Cython
+    satella
+
+python_requires = !=2.7.*,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*
+packages = snakehouse
+
+
+[options.packages.find]
+exclude =
+    docs
+    example
+
+[pycodestyle]
+max-line-length = 100
+
+[pep8]
+max-line-length = 100
+
+[bdist_wheel]
+universal = 0
+
+[aliases]
+test=pytest
\ No newline at end of file
diff --git a/setup.py b/setup.py
index b024da80e9c1c8c800cc1b46e90c1e783cb446cc..93213959e1b316e30d2d1de32c43335306647c69 100644
--- a/setup.py
+++ b/setup.py
@@ -1,4 +1,4 @@
-from setuptools import setup
-
-
-setup()
+from setuptools import setup
+
+
+setup(tests_require=['pytest'])
diff --git a/snakehouse/__init__.py b/snakehouse/__init__.py
index 70f9ed1c2e9b9e3bf048fe7bce9f43b8e575e788..f9b0063608886d64f25c63d13ac6c6e9fb9cfbca 100644
--- a/snakehouse/__init__.py
+++ b/snakehouse/__init__.py
@@ -1,10 +1,10 @@
-import pkg_resources
-from .build import build
-from .multibuild import Multibuild, find_all
-from .faster_builds import monkey_patch_parallel_compilation
-from .requirements import read_requirements_txt, find_c, find_pyx_and_c, find_pyx
-
-try:
-    __version__ = pkg_resources.require('snakehouse')[0].version
-except pkg_resources.DistributionNotFound:
-    __version__ = '1.7'
+import pkg_resources
+from .build import build
+from .multibuild import Multibuild, find_all
+from .faster_builds import monkey_patch_parallel_compilation
+from .requirements import read_requirements_txt, find_c, find_pyx_and_c, find_pyx
+
+try:
+    __version__ = pkg_resources.require('snakehouse')[0].version
+except pkg_resources.DistributionNotFound:
+    __version__ = '1.7'
diff --git a/snakehouse/templates/bootstrap.mako b/snakehouse/bootstrap.py
similarity index 51%
rename from snakehouse/templates/bootstrap.mako
rename to snakehouse/bootstrap.py
index 9bebeb0a03d3e4864abe59f0d3477fa30a3bbb36..989aa56adf3f60a59486206de5c06c443bbf5192 100644
--- a/snakehouse/templates/bootstrap.mako
+++ b/snakehouse/bootstrap.py
@@ -1,5 +1,8 @@
-import sys
+from .templating import Output
 
+def get_file(cdef_sections=None, get_definition_sections=None, module_set=None):
+    output = Output()
+    output.append_many("""import sys
 cdef extern from "Python.h":
     ctypedef struct PyModuleDef:
         const char* m_name;
@@ -7,29 +10,29 @@ cdef extern from "Python.h":
     void Py_INCREF(object)
     object PyModule_FromDefAndSpec(PyModuleDef *definition, object spec)
     int PyModule_ExecDef(object module, PyModuleDef* definition)
+""")
+    for cdef_section in cdef_sections:
+        output.append_many("""
+cdef extern from "{cdef_section.h_file_name}":
+    object PyInit_{cdef_section.coded_module_name}()
+""", cdef_section=cdef_section)
 
-% for cdef_section in cdef_sections:
-cdef extern from "${cdef_section.h_file_name}":
-    object PyInit_${cdef_section.coded_module_name}()
-% endfor
-
-cdef object get_definition_by_name(str name):
-% for i, getdef_section in enumerate(get_definition_sections):
-% if i == 0:
-    if name == "${getdef_section.module_name}":
-        return PyInit_${getdef_section.coded_module_name}()
-% else:
-    elif name == "${getdef_section.module_name}":
-        return PyInit_${getdef_section.coded_module_name}()
-% endif
-% endfor
-
+    output.append('cdef object get_definition_by_name(str name):')
+    for i, getdef_section in enumerate(get_definition_sections):
+        if i == 0:
+            output.append_many("""
+    if name == "{getdef_section.module_name}":
+        return PyInit_{getdef_section.coded_module_name}()""", getdef_section=getdef_section)
+        else:
+            output.append_many(f"""    elif name == "{getdef_section.module_name}":
+        return PyInit_{getdef_section.coded_module_name}()""", getdef_section=getdef_section)
 
+    output.append_many("""
 cdef class CythonPackageLoader:
     cdef PyModuleDef* definition
     cdef object def_o
     cdef str name
-
+ 
     def __init__(self, name):
         self.def_o = get_definition_by_name(name)
         self.definition = <PyModuleDef*>self.def_o
@@ -47,10 +50,9 @@ cdef class CythonPackageLoader:
     def exec_module(self, module):
         PyModule_ExecDef(module, self.definition)
 
-
 class CythonPackageMetaPathFinder:
     def __init__(self, modules_set):
-        self.modules_set = modules_set
+        self.modules_set = frozenset(modules_set)
 
     def find_module(self, fullname, path):
         if fullname not in self.modules_set:
@@ -61,5 +63,7 @@ class CythonPackageMetaPathFinder:
         pass
 
 def bootstrap_cython_submodules():
-    modules_set = ${module_set}
-    sys.meta_path.append(CythonPackageMetaPathFinder(modules_set))
+    sys.meta_path.append(CythonPackageMetaPathFinder({module_set}))
+""", module_set=module_set)
+
+    return str(output)
\ No newline at end of file
diff --git a/snakehouse/build.py b/snakehouse/build.py
index 5d7186831804e281f1adb202e9ed2619b148b5ea..f9dd3437af41a1313f5e5c49be541dc152c17dfe 100644
--- a/snakehouse/build.py
+++ b/snakehouse/build.py
@@ -1,36 +1,38 @@
-import logging
-import multiprocessing
-import sys
-import typing as tp
-from Cython.Build import cythonize
-from setuptools import Extension
-from .multibuild import Multibuild
-
-MultiBuildType = tp.Union[Multibuild, Exception]
-
-logger = logging.getLogger(__name__)
-
-
-def build(extensions: tp.List[MultiBuildType], *args, nthreads=None, **kwargs):
-    if nthreads is None:
-        nthreads = multiprocessing.cpu_count()
-    if sys.platform == 'win32':
-        print('Sorry, multiprocessing is not yet supported on Windows')
-        nthreads = 0
-    kwargs['nthreads'] = nthreads
-    returns = []
-    multi_builds = []
-    for multi_build in extensions:
-        if isinstance(multi_build, Extension):
-            returns.append(multi_build)
-        elif isinstance(multi_build, Multibuild):
-            multi_build.generate()
-            multi_builds.append(multi_build)
-            returns.extend(multi_build.for_cythonize())
-        else:
-            raise ValueError('Invalid value in list, expected either an instance of Multibuild '
-                             'or an Extension')
-    values = cythonize(returns, *args, **kwargs)
-    for multi_build in multi_builds:
-        multi_build.do_after_cython()
-    return values
+import logging
+import multiprocessing
+import sys
+import typing as tp
+from Cython.Build import cythonize
+from setuptools import Extension
+from .multibuild import Multibuild
+
+MultiBuildType = tp.Union[Multibuild, Exception]
+
+logger = logging.getLogger(__name__)
+
+
+def build(extensions: tp.List[MultiBuildType], *args, nthreads=None,
+          debug: bool = False, **kwargs):
+    """A call to build things. """
+    if nthreads is None:
+        nthreads = multiprocessing.cpu_count()
+    if sys.platform == 'win32':
+        print('Sorry, multiprocessing is not yet supported on Windows')
+        nthreads = 0
+    kwargs['nthreads'] = nthreads
+    returns = []
+    multi_builds = []
+    for multi_build in extensions:
+        if isinstance(multi_build, Extension):
+            returns.append(multi_build)
+        elif isinstance(multi_build, Multibuild):
+            multi_build.generate()
+            multi_builds.append(multi_build)
+            returns.extend(multi_build.for_cythonize())
+        else:
+            raise ValueError('Invalid value in list, expected either an instance of Multibuild '
+                             'or an Extension')
+    values = cythonize(returns, *args, **kwargs)
+    for multi_build in multi_builds:
+        multi_build.do_after_cython()
+    return values
diff --git a/snakehouse/faster_builds.py b/snakehouse/faster_builds.py
index 8c1d90eb8fc48a05b8b37ab43d29f06bb77b7a8f..0189751ddcf170172fba439db45412407879e35c 100644
--- a/snakehouse/faster_builds.py
+++ b/snakehouse/faster_builds.py
@@ -1,49 +1,49 @@
-import multiprocessing
-import typing as tp
-import sys
-
-__all__ = ['monkey_patch_parallel_compilation']
-
-
-def monkey_patch_parallel_compilation(cores: tp.Optional[int] = None) -> None:
-    """
-    This monkey-patches distutils to provide parallel compilation, even if you have
-    a single extension built from multiple .c files.
-
-    Invoke in your setup.py file
-
-    :param cores: amount of cores. Leave at default (None) for autodetection.
-    """
-    if sys.platform == 'win32':
-        print('Sorry, parallel builds are not supported on Windows')
-        return
-
-    if cores is None:
-        cores = multiprocessing.cpu_count()
-
-    # monkey-patch for parallel compilation
-    def parallelCCompile(self, sources, output_dir=None, macros=None, include_dirs=None, debug=0,
-                         extra_preargs=None, extra_postargs=None, depends=None):
-        # those lines are copied from distutils.ccompiler.CCompiler directly
-        macros, objects, extra_postargs, pp_opts, build = self._setup_compile(output_dir, macros,
-                                                                              include_dirs, sources,
-                                                                              depends,
-                                                                              extra_postargs)
-        cc_args = self._get_cc_args(pp_opts, debug, extra_preargs)
-        # parallel code
-        import multiprocessing.pool
-
-        def single_compile(obj):
-            try:
-                src, ext = build[obj]
-            except KeyError:
-                return
-            self._compile(obj, src, ext, cc_args, extra_postargs, pp_opts)
-
-        # evaluate everything
-        for _ in multiprocessing.pool.ThreadPool(cores).imap(single_compile, objects):
-            pass
-        return objects
-
-    import distutils.ccompiler
-    distutils.ccompiler.CCompiler.compile = parallelCCompile
+import multiprocessing
+import typing as tp
+import sys
+
+__all__ = ['monkey_patch_parallel_compilation']
+
+
+def monkey_patch_parallel_compilation(cores: tp.Optional[int] = None) -> None:
+    """
+    This monkey-patches distutils to provide parallel compilation, even if you have
+    a single extension built from multiple .c files.
+
+    Invoke in your setup.py file
+
+    :param cores: amount of cores. Leave at default (None) for autodetection.
+    """
+    if sys.platform == 'win32':
+        print('Sorry, parallel builds are not supported on Windows')
+        return
+
+    if cores is None:
+        cores = multiprocessing.cpu_count()
+
+    # monkey-patch for parallel compilation
+    def parallelCCompile(self, sources, output_dir=None, macros=None, include_dirs=None, debug=0,
+                         extra_preargs=None, extra_postargs=None, depends=None):
+        # those lines are copied from distutils.ccompiler.CCompiler directly
+        macros, objects, extra_postargs, pp_opts, build = self._setup_compile(output_dir, macros,
+                                                                              include_dirs, sources,
+                                                                              depends,
+                                                                              extra_postargs)
+        cc_args = self._get_cc_args(pp_opts, debug, extra_preargs)
+        # parallel code
+        import multiprocessing.pool
+
+        def single_compile(obj):
+            try:
+                src, ext = build[obj]
+            except KeyError:
+                return
+            self._compile(obj, src, ext, cc_args, extra_postargs, pp_opts)
+
+        # evaluate everything
+        for _ in multiprocessing.pool.ThreadPool(cores).imap(single_compile, objects):
+            pass
+        return objects
+
+    import distutils.ccompiler
+    distutils.ccompiler.CCompiler.compile = parallelCCompile
diff --git a/snakehouse/multibuild.py b/snakehouse/multibuild.py
index 1ec2a33cdf2bb92a07077d5ff60682f8087caf95..b62f63a03c1d16c168ea734d361d833ab113fd50 100644
--- a/snakehouse/multibuild.py
+++ b/snakehouse/multibuild.py
@@ -1,250 +1,293 @@
-import hashlib
-import itertools
-import os
-import logging
-import collections
-import typing as tp
-import warnings
-
-import pkg_resources
-from satella.files import split, find_files
-from mako.template import Template
-from setuptools import Extension
-
-
-CdefSection = collections.namedtuple('CdefSection', ('h_file_name', 'module_name', 'coded_module_name'))
-GetDefinitionSection = collections.namedtuple('GetDefinitionSection', (
-    'module_name', 'pyinit_name', 'coded_module_name'
-))
-
-logger = logging.getLogger(__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:
-    tpl = Template(pkg_resources.resource_string(
-        'snakehouse', os.path.join('templates', template_name)).decode('utf8'))
-    return tpl.render(**kwargs)
-
-
-LINES_IN_HFILE = len(load_mako_lines('hfile.mako').split('\n'))
-
-
-class find_all:
-    """
-    A directive for :class:`snakehouse.Multibuild` to locate all .pyx
-    files, and possibly all the .c files depending on the switch
-
-    :param directory: base directory to look for files in
-    :param include_c_files: whether to also hook up the located .c files (default False)
-    :param only_c_files: whether to look up only .c files (default False)
-    """
-
-    def __init__(self, directory: str, include_c_files: bool = False,
-                 only_c_files: bool = False):
-        self.dir = directory
-        self.include_c_files = include_c_files
-        self.only_c_files = only_c_files
-
-    def __iter__(self):
-        if self.only_c_files:
-            pyx_files = []
-        else:
-            pyx_files = find_files(self.dir, r'(.*)\.pyx', scan_subdirectories=True)
-
-        if self.include_c_files:
-            pyx_files = itertools.chain(pyx_files,
-                                        find_files(self.dir, r'(.*)\.c', scan_subdirectories=True))
-        return pyx_files
-
-
-class Multibuild:
-    """
-    This specifies a single Cython extension, called {extension_name}.__bootstrap__
-
-    All kwargs will be sent straight to Cython's Extension
-
-    :param extension_name: the module name
-    :param files: list of pyx and c files
-    :param kwargs: extra arguments to be passed to Extension() object
-    :param dont_snakehouse: snakehouse won't be enabled, each element will be built
-      as a separate extension. It is for these cases when you're testing and something segfaults.
-    """
-    def __init__(self, extension_name: str, files: tp.Iterator[str],
-                 dont_snakehouse: bool = False,
-                 **kwargs):
-        # sanitize path separators so that Linux-style paths are supported on Windows
-        logger.warning('Building extension %s with files %s', extension_name, files)
-        files = list(files)
-        self.dont_snakehouse = dont_snakehouse
-        self.kwargs = kwargs
-        if 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.pyx_files = [file for file in self.files if file.endswith('.pyx')]
-            self.c_files = [file for file in self.files if file.endswith('.c') or file.endswith('.cpp')]
-        else:
-            self.pyx_files = []
-            self.c_files = []
-
-        self.do_generate = True
-        if not self.pyx_files:
-            warnings.warn('No pyx files, probably installing from a source archive, skipping '
-                          'generating files', RuntimeWarning)
-            self.do_generate = False
-        else:
-            self.extension_name = extension_name        # type: str
-            if len(self.files) == 1:
-                self.bootstrap_directory, _ = os.path.split(self.files[0])      # type: str
-            else:
-                self.bootstrap_directory = os.path.commonpath(self.files)       # type: 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):
-        for filename in self.pyx_files:
-            path, name, cmod_name_path, module_name, coded_module_name, complete_module_name = self.transform_module_name(filename)
-            if not name.endswith('.pyx'):
-                continue
-
-            h_file = filename.replace('.pyx', '.h')
-
-            if os.path.exists(h_file):
-                with open(h_file, 'r') as f_in:
-                    data = f_in.readlines()
-
-                linesep = 'cr' if '\r\n' in data[0] else 'lf'
-                rendered_mako = render_mako('hfile.mako', initpy_name=coded_module_name) + \
-                                '\r\n' if linesep == 'cr' else '\n'
-                assert len(rendered_mako) > 0
-
-                if any('#define SNAKEHOUSE_FILE' in line for line in data):
-                    data = [rendered_mako, *data[LINES_IN_HFILE:]]
-                else:
-                    data = [rendered_mako, *data]
-            else:
-                rendered_mako = render_mako('hfile.mako', initpy_name=coded_module_name)
-                assert len(rendered_mako) > 0
-                data = rendered_mako
-
-            with open(h_file, 'w') as f_out:
-                f_out.write(''.join(data))
-
-    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):
-        if self.dont_snakehouse:
-            return
-        self.generate_header_files()
-        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)
-
-    def generate_bootstrap(self) -> str:
-
-        cdef_section = []
-        for filename in self.pyx_files:
-            path, name, cmod_name_path, module_name, coded_module_name, complete_module_name = self.transform_module_name(filename)
-
-            if os.path.exists(filename.replace('.pyx', '.c')):
-                os.unlink(filename.replace('.pyx', '.c'))
-
-            h_path_name = os.path.join(cmod_name_path, name.replace('.pyx', '.h')).replace('\\', '\\\\')
-
-            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 = []
-        for mod_name, init_fun_name, coded_module_name in self.modules:
-            get_definition.append(GetDefinitionSection(mod_name, init_fun_name, coded_module_name))
-
-        return render_mako('bootstrap.mako', cdef_sections=cdef_section,
-                                             get_definition_sections=get_definition,
-                                             module_set=repr(set(x[0] for x in self.modules)))
-
-    def write_bootstrap_file(self):
-        with open(os.path.join(self.bootstrap_directory, '__bootstrap__.pyx'), 'w') as f_out:
-            f_out.write(self.generate_bootstrap())
-
-    def alter_init(self):
-        if os.path.exists(os.path.join(self.bootstrap_directory, '__init__.py')):
-            with open(os.path.join(self.bootstrap_directory, '__init__.py'), 'r') as f_in:
-                data = f_in.read()
-        else:
-            data = ''
-
-        if 'bootstrap_cython_submodules' not in data:
-            data = render_mako('initpy.mako', module_name=self.extension_name) + data
-
-        with open(os.path.join(self.bootstrap_directory, '__init__.py'), 'w') as f_out:
-            f_out.write(data)
-
-    def generate(self):
-        if not self.dont_snakehouse and self.do_generate:
-            self.write_bootstrap_file()
-            self.alter_init()
-
-    def for_cythonize(self, *args, **kwargs):
-        if self.dont_snakehouse:
-            extensions = []
-            len_to_sub = len(self.bootstrap_directory) + len(os.path.pathsep)
-            for pyx_file in self.pyx_files:
-                file_name = pyx_file[len_to_sub:-4].replace('\\', '.').replace('/', '.')
-                ext = Extension(self.extension_name+'.'+file_name,
-                                [pyx_file] + self.c_files, *args, **kwargs)
-                extensions.append(ext)
-            return extensions
-        else:
-            kwargs.update(self.kwargs)
-            for_cythonize = [*self.files, os.path.join(self.bootstrap_directory, '__bootstrap__.pyx')]
-            return [Extension(self.extension_name+".__bootstrap__",
-                              for_cythonize,
-                              *args,
-                              **kwargs)]
+import hashlib
+import itertools
+import os
+import logging
+import collections
+import typing as tp
+import warnings
+
+import pkg_resources
+from satella.files import split, find_files
+from setuptools import Extension
+from .templating import HFILE_MAKO, INITPY_MAKO
+from. bootstrap import get_file
+
+
+CdefSection = collections.namedtuple('CdefSection', ('h_file_name', 'module_name', 'coded_module_name'))
+GetDefinitionSection = collections.namedtuple('GetDefinitionSection', (
+    'module_name', 'pyinit_name', 'coded_module_name'
+))
+
+logger = logging.getLogger(__name__)
+
+
+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
+
+
+LINES_IN_HFILE = len(HFILE_MAKO.split('\n'))
+
+
+class find_all:
+    """
+    A directive for :class:`snakehouse.Multibuild` to locate all .pyx
+    files, and possibly all the .c files depending on the switch
+
+    :param directory: base directory to look for files in
+    :param include_c_files: whether to also hook up the located .c files (default False)
+    :param only_c_files: whether to look up only .c files (default False)
+    """
+
+    def __init__(self, directory: str, include_c_files: bool = False,
+                 only_c_files: bool = False):
+        self.dir = directory
+        self.include_c_files = include_c_files
+        self.only_c_files = only_c_files
+
+    def __iter__(self):
+        if self.only_c_files:
+            pyx_files = []
+        else:
+            pyx_files = find_files(self.dir, r'(.*)\.pyx', scan_subdirectories=True)
+
+        if self.include_c_files:
+            pyx_files = itertools.chain(pyx_files,
+                                        find_files(self.dir, r'(.*)\.c', scan_subdirectories=True))
+        return pyx_files
+
+class BreakException(Exception):
+    pass
+class Multibuild:
+    """
+    This specifies a single Cython extension, called {extension_name}.__bootstrap__
+
+    All kwargs will be sent straight to Cython's Extension
+
+    :param extension_name: the module name
+    :param files: list of pyx and c files
+    :param kwargs: extra arguments to be passed to Extension() object
+    :param dont_snakehouse: snakehouse won't be enabled, each element will be built
+      as a separate extension. It is for these cases when you're testing and something segfaults.
+    """
+    def __init__(self, extension_name: str, files: tp.Iterator[str],
+                 dont_snakehouse: bool = False,
+                 **kwargs):
+        # sanitize path separators so that Linux-style paths are supported on Windows
+        logger.warning('Building extension %s with files %s', extension_name, files)
+        files = list(files)
+        self.dont_snakehouse = dont_snakehouse
+        self.kwargs = kwargs
+        self.clear_all_bootstraps()
+        if 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.pyx_files = [file for file in self.files if file.endswith('.pyx')]
+            self.c_files = [file for file in self.files if file.endswith('.c') or file.endswith('.cpp')]
+        else:
+            self.pyx_files = []
+            self.c_files = []
+
+        self.do_generate = True
+        if not self.pyx_files:
+            warnings.warn('No pyx files, probably installing from a source archive, skipping '
+                          'generating files', RuntimeWarning)
+            self.do_generate = False
+        else:
+            self.extension_name = extension_name        # type: str
+            if len(self.files) == 1:
+                self.bootstrap_directory, _ = os.path.split(self.files[0])      # type: str
+            else:
+                self.bootstrap_directory = os.path.commonpath(self.files)       # type: 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 fix_all_c_files(self):
+        return
+        for root, dirs, files in os.walk("."):
+            try:
+                for c_name in files:
+                    for allowed in ['.h', '.c', '.pyx', '.pxd']:
+                        if not c_name.endswith(allowed):
+                            raise BreakException
+                    name = os.path.join(root, c_name)
+                    if c_name == '__bootstrap__.c':
+                        print('Skipping %s' % (c_name, ))
+                        continue
+
+                    if not c_name.endswith('.c'):
+                        continue
+                    with open(name, 'r', encoding='utf-8') as fin:
+                        data = fin.read()
+                        is_windows = '\r\n' in data
+
+                    with open(name, 'w', encoding='utf-8') as fout:
+                        c_name = c_name.replace('.c', '.h')
+                        fout.write('#include "%s"' % (c_name, ))
+                        fout.write('\r\n' if is_windows else '\n')
+                        fout.write(data)
+            except BreakException:
+                pass
+
+    def generate_header_files(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)
+            if not name.endswith('.pyx'):
+                continue
+
+            h_file = filename.replace('.pyx', '.h')
+
+            if os.path.exists(h_file):
+                with open(h_file, 'r', encoding='utf-8') as f_in:
+                    data = f_in.readlines()
+                    print(f'writing {data} to {h_file}')
+                data = ['#include "Python.h"\n']
+                rendered_mako = HFILE_MAKO.format(initpy_name=coded_module_name)
+
+                if any('#define SNAKEHOUSE_FILE' in line for line in data):
+                    data = [*data, rendered_mako]
+                else:
+                    data = [*data, rendered_mako]
+            else:
+                rendered_mako = HFILE_MAKO.format(initpy_name=coded_module_name)
+                assert len(rendered_mako) > 0
+                data = '#include "Python.h"\n' + rendered_mako
+
+            with open(h_file, 'w') as f_out:
+                f_out.write(''.join(data))
+
+    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):
+        if self.dont_snakehouse:
+            return
+        self.generate_header_files()
+        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)
+        for root, dirs, files in os.walk('.'):
+            for file in files:
+                c_name = os.path.join(root, file)
+                if file == '__bootstrap__.c':
+                    with open(c_name, 'r', encoding='utf-8') as fin:
+                        data = fin.read()
+                        data = data.replace('__Pyx_PyMODINIT_FUNC PyInit___bootstrap__(void)',
+                                            'PyMODINIT_FUNC PyInit__'+coded_module_name+'(void)')
+                        data = data.replace('__Pyx_PyMODINIT_FUNC init__bootstrap__(void)',
+                                            'PyMODINIT_FUNC init__'+coded_module_name+'(void)')
+                    with open(c_name, 'w', encoding='utf-8') as fout:
+                        for filename in self.pyx_files:
+                            path, name, cmod_name_path, module_name, coded_module_name, complete_module_name = self.transform_module_name(
+                                filename)
+                            h_path_name = os.path.join(cmod_name_path, name.replace('.pyx', '.h')).replace('\\', '\\\\')
+                            fout.write('#include "%s"\n' % (h_path_name, ))
+                        fout.write(data)
+                c_name = os.path.join(root, file)
+
+    def generate_bootstrap(self) -> str:
+
+        cdef_section = []
+        for filename in self.pyx_files:
+            path, name, cmod_name_path, module_name, coded_module_name, complete_module_name = self.transform_module_name(filename)
+
+            if os.path.exists(filename.replace('.pyx', '.c')):
+                os.unlink(filename.replace('.pyx', '.c'))
+
+            h_path_name = os.path.join(cmod_name_path, name.replace('.pyx', '.h')).replace('\\', '\\\\')
+
+            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 = []
+        for mod_name, init_fun_name, coded_module_name in self.modules:
+            get_definition.append(GetDefinitionSection(mod_name, init_fun_name, coded_module_name))
+
+        return get_file(cdef_sections=cdef_section, get_definition_sections=get_definition,
+                        module_set=[x[0] for x in self.modules])
+
+    def clear_all_bootstraps(self):
+        for root, dirs, files in os.walk('.'):
+            for file in files:
+                c_name = os.path.join(root, file)
+                if c_name.startswith('__bootstrap__'):
+                    os.unlink(c_name)
+
+    def write_bootstrap_file(self):
+        with open(os.path.join(self.bootstrap_directory, '__bootstrap__.pyx'), 'w') as f_out:
+            f_out.write(self.generate_bootstrap())
+
+    def alter_init(self):
+        if os.path.exists(os.path.join(self.bootstrap_directory, '__init__.py')):
+            with open(os.path.join(self.bootstrap_directory, '__init__.py'), 'r') as f_in:
+                data = f_in.read()
+        else:
+            data = ''
+
+        if 'bootstrap_cython_submodules' not in data:
+            data = INITPY_MAKO.format(module_name=self.extension_name) + data
+
+        with open(os.path.join(self.bootstrap_directory, '__init__.py'), 'w') as f_out:
+            f_out.write(data)
+
+    def generate(self):
+        if not self.dont_snakehouse and self.do_generate:
+            self.write_bootstrap_file()
+            self.alter_init()
+
+    def for_cythonize(self, debug: bool = False, *args, **kwargs):
+        if self.dont_snakehouse:
+            extensions = []
+            len_to_sub = len(self.bootstrap_directory) + len(os.path.pathsep)
+            for pyx_file in self.pyx_files:
+                file_name = pyx_file[len_to_sub:-4].replace('\\', '.').replace('/', '.')
+                ext = Extension(self.extension_name+'.'+file_name,
+                                [pyx_file] + self.c_files, *args, **kwargs)
+                extensions.append(ext)
+            return extensions
+        else:
+            kwargs.update(self.kwargs)
+            for_cythonize = list(set([*self.files, os.path.join(self.bootstrap_directory, '__bootstrap__.pyx')]))
+            print(f'Rolling for cythonize {for_cythonize}')
+            return [Extension(self.extension_name+".__bootstrap__",
+                              for_cythonize,
+                              *args,
+                              **kwargs)]
diff --git a/snakehouse/requirements.py b/snakehouse/requirements.py
index 85eb4e8bddee486671960f6646d4871d8889847f..1e8fbe03d5ec48a6ec0f1b4796a5c24c5591afc3 100644
--- a/snakehouse/requirements.py
+++ b/snakehouse/requirements.py
@@ -1,64 +1,62 @@
-import typing as tp
-import warnings
-
-from satella.coding import for_argument
-from satella.files import read_lines, find_files
-
-
-@for_argument(returns=list)
-def find_pyx(directory_path: str) -> tp.List[str]:
-    """
-    Return all .pyx files found in given directory.
-
-    :param directory_path: directory to look through
-    :return: .pyx files found
-    """
-    warnings.warn('This is deprecated. Use find_all instead', DeprecationWarning)
-    return find_files(directory_path, '(.*)\\.pyx$', scan_subdirectories=True)
-
-
-@for_argument(returns=list)
-def find_c(directory_path: str) -> tp.List[str]:
-    """
-    Return all .c files found in given directory.
-
-    :param directory_path: directory to look through
-    :return: .c files found
-    """
-    warnings.warn('This is deprecated. Use find_all instead', DeprecationWarning)
-    return find_files(directory_path, '(.*)\\.c$', scan_subdirectories=True)
-
-
-def find_pyx_and_c(directory_path: str) -> tp.List[str]:
-    """
-    Return a list of all .pyx and .c files found in given directory.
-
-    :param directory_path:
-    :return: list of all .pyx and .c files found in given directory
-    """
-    warnings.warn('This is deprecated. Use find_all instead', DeprecationWarning)
-    files = find_pyx(directory_path)
-    files.extend(find_c(directory_path))
-    return files
-
-
-def read_requirements_txt(path: str = 'requirements.txt'):
-    """
-    Read requirements.txt and parse it into a list of packages
-    as given by setup(install_required=).
-
-    This means it will read in all lines, discard empty and commented ones,
-    and discard all those who are an URL.
-
-    Remember to include your requirements.txt inside your MANIFEST.in!
-
-    :param path: path to requirements.txt. Default is `requirements.txt`.
-    :return: list of packages ready to be fed to setup(install_requires=)
-    """
-    lines = read_lines(path)
-    lines = (line.strip() for line in lines)
-    lines = (line for line in lines if not line.startswith('#'))
-    lines = (line for line in lines if not line.startswith('git+'))
-    lines = (line for line in lines if not line.startswith('http'))
-    lines = (line for line in lines if line)
-    return list(lines)
+import typing as tp
+import warnings
+
+from satella.coding import for_argument
+from satella.files import read_lines, find_files
+
+
+def find_pyx(directory_path: str) -> tp.List[str]:
+    """
+    Return all .pyx files found in given directory.
+
+    :param directory_path: directory to look through
+    :return: .pyx files found
+    """
+    warnings.warn('This is deprecated. Use find_all instead', DeprecationWarning)
+    return list(find_files(directory_path, '(.*)\\.pyx$', scan_subdirectories=True))
+
+
+def find_c(directory_path: str) -> tp.List[str]:
+    """
+    Return all .c files found in given directory.
+
+    :param directory_path: directory to look through
+    :return: .c files found
+    """
+    warnings.warn('This is deprecated. Use find_all instead', DeprecationWarning)
+    return list(find_files(directory_path, '(.*)\\.c$', scan_subdirectories=True))
+
+
+def find_pyx_and_c(directory_path: str) -> tp.List[str]:
+    """
+    Return a list of all .pyx and .c files found in given directory.
+
+    :param directory_path:
+    :return: list of all .pyx and .c files found in given directory
+    """
+    warnings.warn('This is deprecated. Use find_all instead', DeprecationWarning)
+    files = find_pyx(directory_path)
+    files.extend(find_c(directory_path))
+    return files
+
+
+def read_requirements_txt(path: str = 'requirements.txt'):
+    """
+    Read requirements.txt and parse it into a list of packages
+    as given by setup(install_required=).
+
+    This means it will read in all lines, discard empty and commented ones,
+    and discard all those who are an URL.
+
+    Remember to include your requirements.txt inside your MANIFEST.in!
+
+    :param path: path to requirements.txt. Default is `requirements.txt`.
+    :return: list of packages ready to be fed to setup(install_requires=)
+    """
+    lines = read_lines(path)
+    lines = (line.strip() for line in lines)
+    lines = (line for line in lines if not line.startswith('#'))
+    lines = (line for line in lines if not line.startswith('git+'))
+    lines = (line for line in lines if not line.startswith('http'))
+    lines = (line for line in lines if line)
+    return list(lines)
diff --git a/snakehouse/templates/hfile.mako b/snakehouse/templates/hfile.mako
deleted file mode 100644
index 309812b7f7593138b18e9df01979dd32c929763c..0000000000000000000000000000000000000000
--- a/snakehouse/templates/hfile.mako
+++ /dev/null
@@ -1,3 +0,0 @@
-#include "Python.h"
-#define SNAKEHOUSE_FILE
-PyObject* PyInit_${initpy_name}(void);
diff --git a/snakehouse/templates/initpy.mako b/snakehouse/templates/initpy.mako
deleted file mode 100644
index 020fe76eeae514935ba3395ee559fa37bda17d8c..0000000000000000000000000000000000000000
--- a/snakehouse/templates/initpy.mako
+++ /dev/null
@@ -1,2 +0,0 @@
-from ${module_name}.__bootstrap__ import bootstrap_cython_submodules
-bootstrap_cython_submodules()
diff --git a/snakehouse/templating.py b/snakehouse/templating.py
new file mode 100644
index 0000000000000000000000000000000000000000..17aaf4df0770f91b6460ac64fd5ac363ec3f738c
--- /dev/null
+++ b/snakehouse/templating.py
@@ -0,0 +1,32 @@
+"""A brain-dead implementation of Mako"""
+import re
+import typing as tp
+
+
+FOR_MATCH = re.compile(r'% for (.*?) in (.*?)\:')
+
+HFILE_MAKO = """
+extern PyObject* PyInit_{initpy_name}();
+"""
+
+
+INITPY_MAKO = """from {module_name}.__bootstrap__ import bootstrap_cython_submodules
+bootstrap_cython_submodules()
+"""
+
+class Output:
+    def __init__(self):
+        self.elements_containing = {}   #: type dict[str, list[str]]
+        self.data = []      #: type: str | tuple[str, str, str]
+
+    def __str__(self):
+        return '\n'.join(self.data)
+    def append(self, ostr: str, **kwargs):
+        if kwargs:
+            ostr = ostr.format(**kwargs)
+        self.data.append(ostr)
+    def append_many(self, lines: str, **kwargs):
+        for line in lines.split('\n'):
+            if kwargs:
+                line = line.format(**kwargs)
+            self.data.append(line)