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..748271ddc221f5938e4a8687e5900213db5ff65e
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,11 @@
+
+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
+    - pip install git+https://gitlab-ci-token:${CI_JOB_TOKEN}@i10git.cs.fau.de/pycodegen/pystencils_walberla.git@master#egg=pystencils_walberla
+    - pip install git+https://gitlab-ci-token:${CI_JOB_TOKEN}@i10git.cs.fau.de/pycodegen/lbmpy.git@master#egg=lbmpy
+    - python3 setup.py test
+  tags:
+    - docker
+    - AVX
diff --git a/lbmpy_walberla_tests/test_walberla_codegen.py b/lbmpy_walberla_tests/test_walberla_codegen.py
index fae098137e7778aa1f4a5275d05da2f4d690a31d..2f4ff6831931c672c779311e90749ca14cd53568 100644
--- a/lbmpy_walberla_tests/test_walberla_codegen.py
+++ b/lbmpy_walberla_tests/test_walberla_codegen.py
@@ -6,41 +6,45 @@ from lbmpy_walberla import generate_lattice_model, RefinementScaling, generate_b
 from lbmpy_walberla.sparse import ListLbGenerator
 from pystencils_walberla.cmake_integration import ManualCodeGenerationContext
 from pystencils_walberla import generate_pack_info_for_field, generate_pack_info_from_kernel
-
-
-def test_lattice_model():
-    with ManualCodeGenerationContext() as ctx:
-        force_field = ps.fields("force(3): [3D]", layout='fzyx')
-        omega = sp.Symbol("omega")
-
-        lb_method = create_lb_method(stencil='D3Q19', method='srt', relaxation_rates=[omega],
-                                     force_model='guo', force=force_field.center_vector)
-
-        scaling = RefinementScaling()
-        scaling.add_standard_relaxation_rate_scaling(omega)
-        scaling.add_force_scaling(force_field)
-
-        generate_lattice_model(ctx, 'SrtWithForceFieldModel', lb_method, refinement_scaling=scaling)
-        generate_boundary(ctx, 'MyUBB', UBB([0.05, 0, 0]), lb_method)
-        generate_boundary(ctx, 'MyNoSlip', NoSlip(), lb_method)
-
-
-def test_sparse():
-    from lbmpy.creationfunctions import create_lb_collision_rule
-    from pystencils import show_code
-    g = ListLbGenerator(create_lb_collision_rule())
-    kernel_code = str(show_code(g.kernel()))
-    assert 'num_cells' in kernel_code
-    setter_code = str(show_code(g.setter_ast()))
-    assert 'num_cells' in setter_code
-    getter_code = str(show_code(g.getter_ast()))
-    assert 'num_cells' in getter_code
-
-
-def test_pack_info():
-    with ManualCodeGenerationContext() as ctx:
-        f = ps.fields("f(9): [3D]")
-        generate_pack_info_for_field(ctx, 'MyPackInfo1', f)
-
-        lb_assignments = create_lb_update_rule(stencil='D3Q19', method='srt').main_assignments
-        generate_pack_info_from_kernel(ctx, 'MyPackInfo2', lb_assignments)
+import unittest
+
+
+class WalberlaLbmpyCodegenTest(unittest.TestCase):
+
+    @staticmethod
+    def test_lattice_model():
+        with ManualCodeGenerationContext() as ctx:
+            force_field = ps.fields("force(3): [3D]", layout='fzyx')
+            omega = sp.Symbol("omega")
+
+            lb_method = create_lb_method(stencil='D3Q19', method='srt', relaxation_rates=[omega],
+                                         force_model='guo', force=force_field.center_vector)
+
+            scaling = RefinementScaling()
+            scaling.add_standard_relaxation_rate_scaling(omega)
+            scaling.add_force_scaling(force_field)
+
+            generate_lattice_model(ctx, 'SrtWithForceFieldModel', lb_method, refinement_scaling=scaling)
+            generate_boundary(ctx, 'MyUBB', UBB([0.05, 0, 0]), lb_method)
+            generate_boundary(ctx, 'MyNoSlip', NoSlip(), lb_method)
+
+    @staticmethod
+    def test_sparse():
+        from lbmpy.creationfunctions import create_lb_collision_rule
+        from pystencils import show_code
+        g = ListLbGenerator(create_lb_collision_rule())
+        kernel_code = str(show_code(g.kernel()))
+        assert 'num_cells' in kernel_code
+        setter_code = str(show_code(g.setter_ast()))
+        assert 'num_cells' in setter_code
+        getter_code = str(show_code(g.getter_ast()))
+        assert 'num_cells' in getter_code
+
+    @staticmethod
+    def test_pack_info():
+        with ManualCodeGenerationContext() as ctx:
+            f = ps.fields("f(9): [3D]")
+            generate_pack_info_for_field(ctx, 'MyPackInfo1', f)
+
+            lb_assignments = create_lb_update_rule(stencil='D3Q19', method='srt').main_assignments
+            generate_pack_info_from_kernel(ctx, 'MyPackInfo2', lb_assignments)
diff --git a/setup.py b/setup.py
index f2dfea45ab40508659bf97b42b71fa726ab533a8..6e06b0fa5d2d9d0ada0ecaa98580c91d2b9dda7a 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
+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='lbmpy_walberla',
-      version=get_current_dev_version_from_git(),
+      version=version_number_from_git(),
       description='Code Generation for Lattice Boltzmann Methods in the walberla framework',
       author='Martin Bauer',
       license='AGPLv3',
@@ -16,4 +43,5 @@ setup(name='lbmpy_walberla',
       install_requires=['lbmpy', 'pystencils_walberla'],
       package_dir={'lbmpy_walberla': 'lbmpy_walberla'},
       package_data={'lbmpy_walberla': ['templates/*']},
+      test_suite='pystencils_walberla_tests',
       )