Semantic Versioning and Gitlab

ahmet aşık
Trendyol Tech
Published in
5 min readNov 18, 2020

As the Delivery team at Trendyol we use custom java libraries. When we need to fix something in these common libraries, we need to change pom files of all projects and deploy them again. It’s hard to keep track of which project use these common libraries and change pom files so semantic versioning and maven plugins helped to solve this problem. There are numerous approach for Semantic Versioning and maven tools for this problem but gitlab features satisfy flexibility and simplicity. We tried to use custom approach by using part of GitFlow and Gitlab CI features. Solution may not be suitable for everyone but can show small useful parts.

What is Semantic Versioning?

Semantic versioning is a versioning system that has been on the rise over the last few years. It has always been a problem for software developers, release managers and consumers. Having a universal way of versioning the software development projects is the best way to track what is going on with the software as new plugins, addons, libraries and extensions are being built almost everyday.

What is GitFlow?

GitFlow is a branching model for Git and it is suited to collaboration and scaling the development team.

Semver Flow

There are 6 steps in our approach

  • Create “develop” branch
  • Create feature branch from develop branch and apply your update(s)
  • Trigger pipeline with desired version update(minor or major)
  • Creates automatically release and tag
  • Opens automatically merge request to the develop branch
  • Trigger projects pipelines which uses common library

Before we begin let’s see pom files.

Common Library Pom File


...
<groupId>com.trendyol</groupId>
<artifactId>test</artifactId>
<version>${revision}</version>
<name>test</name>

<properties>
<revision>0.0.1-SNAPSHOT</revision>
</properties>
...

We have property named as “revision”. It has default value as 0.0.1-SNAPSHOT when we generate new version “revision” property will be our environment parameter for versioning.

Project pom file

Key point is that we can give library version as range so when project compiles, version plugin gets last version. Let’s say we have 1.0.1, 1.0.2, 1.0.3 and 1.1.0 if we give range as [1.0.0,1.1.0) it sets version as 1.0.3 and compiles the project. When we create new release for common library, library’s CI triggers projects’ CI and projects fetch automatically the last version of the library according to rule set you have defined in profile section.

...
<properties>
<allowSnapshots>true</allowSnapshots>
<snapshots.enabled>false</snapshots.enabled>
<test.version>null</test.version>
</properties>

<profiles>
<profile>
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
<property>
<name>build_env</name>
<value>dev</value>
</property>
</activation>
<properties>
<test.version>[1.0.0,1.1.0)</test.version>
<snapshots.enabled>true</snapshots.enabled>
<allowSnapshots>true</allowSnapshots>
</properties>
</profile>
<profile>
<id>prod</id>
<activation>
<property>
<name>build_env</name>
<value>prod</value>
</property>
</activation>
<properties>
<test.version>[1.0.0,1.1.0)</test.version>
<snapshots.enabled>false</snapshots.enabled>
<allowSnapshots>false</allowSnapshots>
</properties>
</profile>
...
</profiles>


<repositories>
...
</repositories>

<dependencies>
...
<dependency>
<groupId>com.trendyol</groupId>
<artifactId>test</artifactId>
<version>${test.version}</version>
</dependency>
</dependencies>

<build>
<plugins>
...
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>versions-maven-plugin</artifactId>
<version>2.7</version>
<configuration>
<includes>*metaring*</includes>
</configuration>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>resolve-ranges</goal>
</goals>
</execution>
</executions>
</plugin>
...
</plugins>
</build>
...

Full Script

If current branch name contains “feature” keyword and “DEPLOY_TYPE” parameter exist below steps are applied.

  • It gets last tag if not sets the version as 0.0.1.
  • It increases version according to “DEPLOY_TYPE” parameter. “DEPLOY_TYPE” can be “major” or “minor”.
  • let’s say last tag is 1.2.3 and “DEPLOY_TYPE” parameter is “minor” branch name becomes “release/1.3”. if “DEPLOY_TYPE” parameter is “major” then branch name becomes “release/2.0”.
  • It creates new release branch.

If CI current branch name contains “release” keyword below steps are applied

  • It gets tags and filter by using branch pattern. Let’s say branch name “release/1.3” and tags are 1.1.0, 1.1.2, 1.1.3, 1.2.0, 1.3.0 and 1.3.1 output becomes 1.3.2. If tags are 1.1.0, 1.1.2, 1.1.3 and 1.2.0 output becomes 1.3.0.
  • It creates new tag for next releases.
  • It exports new version parameter to the file.

semver.sh

Dockerfile for semver tool

Example docker file

FROM alpine:3.12.0
# your jdk installiation
#helpersRUN apk add git
RUN apk add bash
RUN apk add curl
RUN apk add jq
#copy semver script
COPY semver.sh .
#create shortcut -> "semver"
RUN mv semver.sh /usr/bin/semver && \
chmod +x /usr/bin/semver

ARG USER_HOME_DIR="/app"

WORKDIR $USER_HOME_DIR

Gitlab CI

If createVersion exist and value is “true” then deploy with

-Drevision=$VERSION param.

stages:
- test
- create
- trigger
Test:
stage: test
script:
- echo "Your Test Stage"
Create Version:
stage: create
image: "custom image that includes semver script"
services:
- docker:19.03.5-dind
artifacts:
reports:
dotenv: build.env
variables:
DOCKER_HOST: "tcp://localhost:2375"
DOCKER_TLS_CERTDIR: ""
SLACK_HOOK: "your slack hook"
SLACK_CHANNEL: "your slack channel"
GITLAB_HOST: "your gitlab host"
GITLAB_API_TOKEN: "your gitlab token for api and git operations you can get from profile->settings->access tokens"
only:
- /^feature.*$/
- /^release.*$/
script:
- semver
- export VERSION=$(cat version)
- if [ "$(cat createVersion)" = true ]; then ./mvnw clean deploy -Drevision=$VERSION -f pom.xml --settings settings.xml; echo "CREATE_VERSION=true" >> build.env; fi
Trigger:
stage: trigger
dependencies:
- create
only:
- /^release.*$/
script:
- if [ "$CREATE_VERSION" != "true" ]; then return 1; fi
- "curl --request POST --form token={token} --form ref=master https://gitlab.example.com/api/v4/projects/{projectId}/trigger/pipeline"

You can add your projects’ trigger at then end of your script like above. Once project pipeline triggered it gets the most current version of common library according to the rule set that defined in your pom file. Curl steps are optional.

  • You need to protect your development branch and release branch.
  • Keep development branch updated (merge opened PR)

Gitlab Result

Initial
Release Stages
Tags

Reference

--

--