diff --git a/.gitignore b/.gitignore
index 8c07d2e7bcd5bd00eaffa7c1f2a9e9a454029119..d59b79f04b6d83904c943c3ea93fdf5546c6c3f1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
 *.jar
+!gradle/wrapper/gradle-wrapper.jar
 .idea/
 .gradle/
 build
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 9729a314e03c730a3f31025a68ae8233c46d0a62..b6bf4858b97068488b069297326a1ec70aeaa19b 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,8 +1,43 @@
 stages:
-  - build
+- build
+- test
+- jar
 
-test:
+build:
   image: openjdk:8
   stage: build
   script:
-    - ./gradlew --no-daemon build
+    - ./gradlew --console=plain --build-cache assemble
+  cache:
+    key: "$CI_COMMIT_REF_NAME"
+    policy: push
+    paths:
+      - build
+      - .gradle
+
+test:
+  image: openjdk:8
+  stage: test
+  script:
+    - ./gradlew --continue --console=plain --info check
+  cache:
+    key: "$CI_COMMIT_REF_NAME"
+    policy: pull
+    paths:
+      - build
+      - .gradle
+
+jar:
+  image: openjdk:8
+  stage: jar
+  script:
+    - ./gradlew --continue --console=plain --info jar
+  cache:
+    key: "$CI_COMMIT_REF_NAME"
+    policy: pull
+    paths:
+      - build
+      - .gradle
+  artifacts:
+    paths:
+      - "/builds/jastadd/*/build/libs/*relast*.jar"
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 4a602406b23eef0e1d78decd1f090a95971c4f75..46997637b262d0b2c2696a43154fc008f4afa3b5 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -2,7 +2,7 @@
 
 To propose a new feature, or to report a bug, first [create an issue][create-issue] and add labels accordingly.
 Working on such issues is done by creating a merge request from the issue page, which 1) creates a new branch to work on, and 2) creates a new WIP merge request for the new branch.
-Once done (and new tests are written to ensure, a bug is really fixed, and the feature does the right thing), the merge request will be accepted and merged into `master`.
+Once done (and new tests are written to ensure, a bug is really fixed, and the feature does the right thing), the merge request will be accepted and merged into `develop`.
 
 # Creating normal test cases
 
@@ -63,7 +63,7 @@ Aside from the [normal tests](#creating-normal-test-cases), there are some speci
 
 ## Negative parser tests
 
-To check, errors are found and contain the correct messages, one test [`Errors`](/../blob/master/src/test/java/org/jastadd/relast/tests/Errors.java) is used.
+To check, errors are found and contain the correct messages, one test [`Errors`][Errors.java] is used.
 Here, the RelAST compiler is invoked manually, and the actual error messages are compared to expected ones for each grammar in `src/test/jastadd/errors`.
 The expected messages can contain the special word `$FILENAME` to refer to the filename of the grammar, if there is only one, or `$FILENAME1`, `$FILENAME2` etc., if there are many.
 Furthermore, empty lines, lines starting with `//` and the order of the error messages are ignored.
@@ -74,14 +74,35 @@ Currently, there is one test to test whether the output of RelAST is a valid inp
 To achieve this, there are two Gradle tasks. The first produces the usual `.ast` and `.jadd` files, whereas the second task takes the `.ast` as input.
 The test then ensures, that both output grammars are identical.
 
-# Publishing (Maintainer only)
-
-Currently, we are publishing to a private Nexus repository only. As a maintainer, to publish a new version, the following needs to be done:
-
-1) Create a new annotated tag with an appropriate version number increase (major, minor, patch) described in [semantic versioning](https://semver.org/) and with "Version $MAJOR.$MINOR.$PATH" as message.
-2) If not already present, create a new file `gradle.properties` with two entries `repoUser` and `repoPassword` for our Nexus repository credentials.
-3) Decide, if the new version should only be a snapshot version (whose contents can be overridden within the same version)
-4) Run `./gradlew publish -PwithNewVersion` (or `./gradlew publish -PwithNewVersion -PasSnapshot` to create a SNAPSHOT release)
-5) (Optional) Update the dependencies in other projects using Relational RAGs.
-
+# Releases and Publishing (Maintainer only)
+
+Important information:
+
+- Currently, we are publishing to a private Nexus Maven repository only.
+- We are using [git-flow][git-flow], so only new merge requests are considered for releases to appear in the `master` branch.
+- The version is set in the configuration file [RelASTVersion.properties][RelASTVersion.properties].
+
+The workflow:
+
+1) Finish your work with the current feature(s) and merge those back in `develop`.
+1) Choose a new version number `$nextVersion` depending on the introduced changes **following [semantic versioning][semantic-versioning]**.
+1) Create a new release branch named `release/$nextVersion` and switch to this branch.
+1) Set the version number in the config file calling `./gradlew newVersion -Pvalue=$nextVersion`
+1) Commit this change.
+1) (Optional) Build a new jar file calling `./gradlew jar` (this is automatically called in the publish step and only used to test the newly set version number)
+1) Check, if everything works as planned, e.g., version number is picked up when running the application with `--version`, and all test succeed.
+1) Merge the release branch into `master` (using a merge request) and also back into `develop`.
+1) Delete the release branch.
+1) [Create a new release][create-release]. Choose the following:
+    - *Tag name*: the chosen version number
+    - *Create from*: leave the default `master`
+    - *Message*: "Version " and the chose version number
+    - *Release notes*: list the (important) changes compared to the last release, prepend a link to the built jar using the line `[:floppy_disk: publish-relast-poc-$nextVersion.jar](/../../../-/jobs/$jobNumber/artifacts/raw/build/libs/publish-relast-poc-$nextVersion.jar?inline=false)` replacing `$jobNumber` with the `jar` job of the pipeline run after the merge request, and `$nextVersion`
+1) Publish the built jar to the maven repository calling `./gradlew publish`
+
+[git-flow]: https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow
+[Errors.java]: /../blob/master/src/test/java/org/jastadd/relast/tests/Errors.java
+[RelASTVersion.properties]: /../-/blob/master/src/main/resources/RelASTVersion.properties
+[semantic-versioning]: https://semver.org/
+[create-release]: /../-/tags/new
 [create-issue]: https://git-st.inf.tu-dresden.de/jastadd/relational-rags/issues/new
diff --git a/build.gradle b/build.gradle
index c9ca7e5e099b54e1e233ee55a4199d83123c9fee..aa950e4f313455285a629e034648c856b4c90d93 100644
--- a/build.gradle
+++ b/build.gradle
@@ -46,42 +46,23 @@ sourceSets {
     }
 }
 
-/* version string handling adapted from https://bitbucket.org/extendj/extendj/src/master/build.gradle
-written by Jesper Öqvist <jesper.oqvist@cs.lth.se> */
 def versionFile = 'src/main/resources/RelASTVersion.properties'
 def oldProps = new Properties()
-String oldFullVersion, fullVersion
 
 try {
     file(versionFile).withInputStream { stream -> oldProps.load(stream) }
-    oldFullVersion = oldProps['version']
-} catch (ignored) {
+    version = oldProps['version']
+} catch (e) {
     // this happens, if either the properties file is not present, or cannot be read from
-    oldFullVersion = "???"
+    throw new GradleException("File ${versionFile} not found or unreadable. Aborting.")
 }
 
-try {
-    def proc = 'git describe'.execute(null, rootDir)
-    if  (proc.waitFor() == 0) {
-        fullVersion = proc.text.trim()
-        if (hasProperty('withNewVersion')) {
-            // Trim to get latest tag
-            version = (fullVersion =~ /-\d+\-g.+$/).replaceAll('')
-            if (hasProperty('asSnapshot')) {
-                version += '-SNAPSHOT'
-            }
-            println("Using version " + version)
-        }
-        if (oldFullVersion != fullVersion) {
-            def props = new Properties()
-            props['version'] = fullVersion
-            props.store(file(versionFile).newWriter(), null)
-        }
-    } else {
-        logger.warn('No git tags found to retrieve version.')
+task newVersion() {
+    doFirst {
+        def props = new Properties()
+        props['version'] = value
+        props.store(file(versionFile).newWriter(), null)
     }
-} catch (IOException e) {
-    logger.warn("Failded to run git describe (${e.getMessage()}) to retrieve version.")
 }
 
 jar {
diff --git a/src/main/java/org/jastadd/relast/compiler/Compiler.java b/src/main/java/org/jastadd/relast/compiler/Compiler.java
index 0ec7a72f54d4853f34742b205dcdcc5eefaba591..dfa3d1a812d0e98a5652a4fe3b324fc17624297a 100644
--- a/src/main/java/org/jastadd/relast/compiler/Compiler.java
+++ b/src/main/java/org/jastadd/relast/compiler/Compiler.java
@@ -23,6 +23,7 @@ public class Compiler {
   private FlagOption optionResolverHelper;
   private FlagOption optionUseJastaddNames;
   private FlagOption optionQuiet;
+  private FlagOption optionVersion;
   private CommandLine commandLine;
 
   public Compiler(String[] args) throws CommandLineException {
@@ -32,6 +33,11 @@ public class Compiler {
     commandLine = new CommandLine(options);
     commandLine.parse(args);
 
+    if (optionVersion.isSet()) {
+      System.out.println(readVersion());
+      return;
+    }
+
     printMessage("Running RelAST " + readVersion());
 
     if (commandLine.getArguments().size() < 1) {
@@ -179,6 +185,7 @@ public class Compiler {
     optionUseJastaddNames = addOption(new FlagOption("useJastAddNames", "generate names in the form of addX, removeX and setX. If omitted, the default, original naming scheme resulting in addToX, removeFromX and setX will be used."));
     optionSerializer = addOption(new EnumOption("serializer", "generate a (de-)serializer", Arrays.asList("jackson", "jackson-json-pointer", "jackson-manual-references"), "jackson"));
     optionQuiet = addOption(new FlagOption("quiet", "do not output anything on stdout"));
+    optionVersion = addOption(new FlagOption("version", "print version and exit"));
   }
 
   private <OptionType extends Option<?>> OptionType addOption(OptionType option) {
diff --git a/src/main/resources/.gitignore b/src/main/resources/.gitignore
deleted file mode 100644
index e4a21f4a3975000dd59369f1430ec3e931b4e3e3..0000000000000000000000000000000000000000
--- a/src/main/resources/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-RelASTVersion.properties
diff --git a/src/main/resources/RelASTVersion.properties b/src/main/resources/RelASTVersion.properties
new file mode 100644
index 0000000000000000000000000000000000000000..303d3960d73d851da19fa2207d452846829cae3e
--- /dev/null
+++ b/src/main/resources/RelASTVersion.properties
@@ -0,0 +1,2 @@
+#Thu Jan 16 09:42:49 CET 2020
+version=0.2.4-28-g22c4762