diff --git a/.flake8 b/.flake8
new file mode 100644
index 0000000000000000000000000000000000000000..4ec3f4456ed1bbdb63b6b71a550099ab6d4e61d0
--- /dev/null
+++ b/.flake8
@@ -0,0 +1,3 @@
+[flake8]
+max-line-length=120
+ignore = W293 W503 W291
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..73c1ace7cf8cf1d79984375aa3f6db811804a3be
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,13 @@
+__pycache__
+.ipynb_checkpoints
+.coverage
+*.pyc
+*.vti
+/build
+/dist
+/*.egg-info
+.cache
+_build
+/.idea
+.cache
+_local_tmp
\ No newline at end of file
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..fce0b7c9c4914ea7346bcf84a85e85b8c6cac86d
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,9 @@
+
+tests:
+  image: i10git.cs.fau.de:5005/software/pystencils/full
+  script:
+    - pip install git+https://gitlab-ci-token:${CI_JOB_TOKEN}@i10git.cs.fau.de/pycodegen/pystencils.git@master#egg=pystencils
+    - python3 setup.py test
+  tags:
+    - docker
+    - AVX
diff --git a/pystencils_walberla_tests/__init__.py b/pystencils_walberla_tests/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pystencils_walberla_tests/test_packinfo_generation.py b/pystencils_walberla_tests/test_packinfo_generation.py
index 38a3bd670b99f30d4d38b7996a492ff802877c35..f0cf9a2acfe92427e9468f8295991f232f8819b2 100644
--- a/pystencils_walberla_tests/test_packinfo_generation.py
+++ b/pystencils_walberla_tests/test_packinfo_generation.py
@@ -1,9 +1,14 @@
 import pystencils as ps
 from pystencils_walberla import generate_pack_info_for_field, generate_pack_info_from_kernel
 from pystencils_walberla.cmake_integration import ManualCodeGenerationContext
+import unittest
 
-def test_packinfo_walberla_gen():
-    for openmp in (False, True):
+
+class PackinfoGenTest(unittest.TestCase):
+
+    @staticmethod
+    def test_packinfo_walberla_gen():
+        for openmp in (False, True):
             for da in (False, True):
                 with ManualCodeGenerationContext(openmp=openmp, double_accuracy=da) as ctx:
                     dtype = "float64" if ctx.double_accuracy else "float32"
@@ -29,4 +34,3 @@ def test_packinfo_walberla_gen():
                             assert 'float ' not in file_to_test
                         else:
                             assert 'double ' not in file_to_test
-
diff --git a/pystencils_walberla_tests/test_walberla_gen.py b/pystencils_walberla_tests/test_walberla_gen.py
index 34ac10b7766831f8a106cc396e3ee7aed46b8b5b..e1caa37e1746f4544693d0942b5c2e6078f225e7 100644
--- a/pystencils_walberla_tests/test_walberla_gen.py
+++ b/pystencils_walberla_tests/test_walberla_gen.py
@@ -2,49 +2,49 @@ import sympy as sp
 import pystencils as ps
 from pystencils_walberla import generate_sweep
 from pystencils_walberla.cmake_integration import ManualCodeGenerationContext
+import unittest
 
-try:
-    import pycuda
-except ImportError:
-    pycuda = None
 
+class CodegenTest(unittest.TestCase):
 
-for openmp in (False, True):
-        for da in (False, True):
-            with ManualCodeGenerationContext(openmp=openmp, double_accuracy=da) as ctx:
-                h = sp.symbols("h")
+    @staticmethod
+    def test_codegen():
+        for openmp in (False, True):
+            for da in (False, True):
+                with ManualCodeGenerationContext(openmp=openmp, double_accuracy=da) as ctx:
+                    h = sp.symbols("h")
 
-                dtype = "float64" if ctx.double_accuracy else "float32"
+                    dtype = "float64" if ctx.double_accuracy else "float32"
 
-                # ----- Jacobi 2D - created by specifying weights in nested list --------------------------
-                src, dst = ps.fields("src, src_tmp: {}[2D]".format(dtype))
-                stencil = [[0, -1, 0],
-                           [-1, 4, -1],
-                           [0, -1, 0]]
-                assignments = ps.assignment_from_stencil(stencil, src, dst, normalization_factor=4 * h**2)
-                generate_sweep(ctx, 'JacobiKernel2D', assignments, field_swaps=[(src, dst)])
+                    # ----- Jacobi 2D - created by specifying weights in nested list --------------------------
+                    src, dst = ps.fields("src, src_tmp: {}[2D]".format(dtype))
+                    stencil = [[0, -1, 0],
+                               [-1, 4, -1],
+                               [0, -1, 0]]
+                    assignments = ps.assignment_from_stencil(stencil, src, dst, normalization_factor=4 * h ** 2)
+                    generate_sweep(ctx, 'JacobiKernel2D', assignments, field_swaps=[(src, dst)])
 
-                # ----- Jacobi 3D - created by using kernel_decorator with assignments in '@=' format -----
-                src, dst = ps.fields("src, src_tmp: {}[3D]".format(dtype))
+                    # ----- Jacobi 3D - created by using kernel_decorator with assignments in '@=' format -----
+                    src, dst = ps.fields("src, src_tmp: {}[3D]".format(dtype))
 
-                @ps.kernel
-                def kernel_func():
-                    dst[0, 0, 0] @= (src[1, 0, 0] + src[-1, 0, 0] +
-                                     src[0, 1, 0] + src[0, -1, 0] +
-                                     src[0, 0, 1] + src[0, 0, -1]) / (6 * h ** 2)
+                    @ps.kernel
+                    def kernel_func():
+                        dst[0, 0, 0] @= (src[1, 0, 0] + src[-1, 0, 0] +
+                                         src[0, 1, 0] + src[0, -1, 0] +
+                                         src[0, 0, 1] + src[0, 0, -1]) / (6 * h ** 2)
 
-                generate_sweep(ctx, 'JacobiKernel3D', kernel_func, field_swaps=[(src, dst)])
+                    generate_sweep(ctx, 'JacobiKernel3D', kernel_func, field_swaps=[(src, dst)])
 
-                expected_files = ('JacobiKernel3D.cpp', 'JacobiKernel3D.h',
-                                  'JacobiKernel2D.cpp', 'JacobiKernel2D.h')
-                assert all(e in ctx.files for e in expected_files)
+                    expected_files = ('JacobiKernel3D.cpp', 'JacobiKernel3D.h',
+                                      'JacobiKernel2D.cpp', 'JacobiKernel2D.h')
+                    assert all(e in ctx.files for e in expected_files)
 
-                for file_name_to_test in ('JacobiKernel3D.cpp', 'JacobiKernel2D.cpp'):
-                    file_to_test = ctx.files[file_name_to_test]
-                    if openmp:
-                        assert '#pragma omp parallel' in file_to_test
+                    for file_name_to_test in ('JacobiKernel3D.cpp', 'JacobiKernel2D.cpp'):
+                        file_to_test = ctx.files[file_name_to_test]
+                        if openmp:
+                            assert '#pragma omp parallel' in file_to_test
 
-                    if da:
-                        assert 'float ' not in file_to_test
-                    else:
-                        assert 'double ' not in file_to_test
+                        if da:
+                            assert 'float ' not in file_to_test
+                        else:
+                            assert 'double ' not in file_to_test
diff --git a/setup.py b/setup.py
index 482b45ebec8171acb0d396d392b55d1550bd664c..056df54d2848918e2acd43f6d866977c71270fff 100644
--- a/setup.py
+++ b/setup.py
@@ -1,12 +1,39 @@
-import os
-import sys
-from setuptools import setup, find_packages
-sys.path.insert(0, os.path.abspath('..'))
-from custom_pypi_index.pypi_index import get_current_dev_version_from_git
+from setuptools import setup
+import subprocess
+
+
+def version_number_from_git(tag_prefix='release/', sha_length=10, version_format="{version}.dev{commits}+{sha}"):
+    def get_released_versions():
+        tags = sorted(subprocess.getoutput('git tag').split('\n'))
+        versions = [t[len(tag_prefix):] for t in tags if t.startswith(tag_prefix)]
+        return versions
+
+    def tag_from_version(v):
+        return tag_prefix + v
+
+    def increment_version(v):
+        parsed_version = [int(i) for i in v.split('.')]
+        parsed_version[-1] += 1
+        return '.'.join(str(i) for i in parsed_version)
+
+    latest_release = get_released_versions()[-1]
+    commits_since_tag = subprocess.getoutput('git rev-list {}..HEAD --count'.format(tag_from_version(latest_release)))
+    sha = subprocess.getoutput('git rev-parse HEAD')[:sha_length]
+    is_dirty = len(subprocess.getoutput("git status --untracked-files=no -s")) > 0
+
+    if int(commits_since_tag) == 0:
+        version_string = latest_release
+    else:
+        next_version = increment_version(latest_release)
+        version_string = version_format.format(version=next_version, commits=commits_since_tag, sha=sha)
+
+    if is_dirty:
+        version_string += ".dirty"
+    return version_string
 
 
 setup(name='pystencils_walberla',
-      version=get_current_dev_version_from_git(),
+      version=version_number_from_git(),
       description='pystencils code generation for waLBerla apps',
       author='Martin Bauer',
       license='AGPLv3',
@@ -15,4 +42,5 @@ setup(name='pystencils_walberla',
       packages=['pystencils_walberla'],
       install_requires=['pystencils[alltrafos]', 'jinja2'],
       package_data={'pystencils_walberla': ['templates/*']},
+      test_suite='pystencils_walberla_tests',
       )