Continuous Versioning with Git and Gradle

Semantic Versaioning

Everybody knows semantic versioning. I think it's still good for software sold in boxes, regardless if the real paper ones or as downloads. But for continuous deployment it does not seem to be good enough.

Looking at the semantic version number does not tell you much. It is barely more than "hey, something was changed and the change is/maybe/should-not be disturbing". Not mentioning that the developers tend to forget to change the version number. And even when they do, it is unnecessarily  difficult to find what exact changes, i.e. commits, are in the semantically versioned release.

Continuous Versioning

Here comes what I call continuous versioning. It's more principle than exact versioning pattern, although I am going to suggest this one:


As you see the semantic version is still there, mainly because people like to see something familiar when you change things :). The main point is to add information about the last commit the change release contains.  The commit timestamp is there to give the version numbers nice chronological ordering. For developers is the most useful the last part - id of the commit, for which we use short hash of Git commit.

As you see there is no big demand for developers to increment the semantic version - it's nice if they do that, but each artifact still gets unique version number if they do not. What's even better, all the information can be gathered during build and used for various artifacts the build can produce - nowadays often executable package and Docker image.

The example how to achieve the described versioning with Gradle and use it to tag a Docker image artifact is below. To get information from Git we use both com.palantir.git-version Gradle plugin and Git command line, because the plugin does not provide timestamp info.

plugins {
    id 'com.palantir.git-version' version '0.12.3'
    id 'com.bmuschko.docker-remote-api' version '6.1.2'

def semanticVersion = '1.0.0'
def gitTimestamp = { ->
    def stdout = new ByteArrayOutputStream()
    exec {
        commandLine 'git', 'show', '-s', '--format=%ct', 'HEAD'
        standardOutput = stdout
    return stdout.toString().trim()
def gitVersion = details.gitHash

version = semanticVersion + "-" + gitTimestamp() + "-" + gitVersion

// ...

import com.bmuschko.gradle.docker.tasks.image.DockerBuildImage

task buildDockerImage(type: DockerBuildImage) {
    // custom task to prepare files ind build/docker directory:
    // dependsOn  "prepareFilesForDockerBuild"
    inputDir = file('build/docker')

import com.bmuschko.gradle.docker.tasks.image.DockerPushImage

task pushDockerImage(type: DockerPushImage) {  
    dependsOn "buildDockerImage"

    registryCredentials {
       url = "mycooldockerrepo.com"
       username = System.getenv('docker_user') ?: "${docker_user}"
       password = System.getenv('docker_password') ?: "${docker_password}"

To package the version info inside the Docker image, we can define custom task prepareFilesForDockerBuild and uncomment the dependsOn line of buildDockerImage().
To save the version number into a file, e.g. named docker/version.txt, this should be inside that task:

new File("docker/version.txt").text = "${version}"

I hope this article helps somebody to look at versioning schemes from a newer point of view. I will add Maven version when I have one but my recent projects seem to be Gradle-only, so it coudl take time - feel free to post yours to share.


Sharing Files From Linux to Windows VM

I have a Linux workstation and created a Windows virtual machine in it, running on KVM+QEMU+libvirt stack. Recently I haveve decided to actually start using this VM and found that to make it comfortable I need some file sharing between the Linux host and Windows guest. Following article describes how to make working, as simple as possible settup to achieve it.

My configuration is Gentoo Linux as the host and Windows 10 a the guest. It should work similarly on any other recent Linux and also on Windows 7. For managing of the VMs I'm using both commandline (mostly virsh and qemu-img) and  Virtual Machine Manager.  

I expect the libvirt and nfs daemons are already running on your machine and you are familiar with basic usage of these tools.

I hope the description bellow will help somebody to save some time. Just don't ask me about systemd setup, I don't use it.  

First of all what approaches I considered and rejected:

  • Plan 9 file sharing protocol - there is no support on Windows side for that
  • Samba - would work but I wanted something simpler

While exploring, what's possible, I found that Windows are able to use NFS and it also seemed as the most simple solution so I decided to give it a try. To my suprise, it really works, although my setup is very simplified. Steps to achieve that in brief:

  • create directory to be shared and export it as NFS volume
  • anable NFS support in Windows and mount the NFS volume

Creating the Directory and Export It

The location of the directory is quite flexible but should be accessible by the account that will be used for the sharing -- I decided to use good old nobody:

>id nobody
uid=65534(nobody) gid=65534(nobody) groups=65534(nobody)

Create the directory and set the ownership:

>mkdir /mnt/diskx/nfsshare
>sudo chown nobody:nobody /mnt/diskx/nfsshare

Let's expect IP address of Windows VM is You can find the real value for your VM  in Virtual Machine manager when you got to Details and look at NIC settings.  For easier manipulation we give it a name by new record in /etc/hosts: windowsvm

Now we need to export the directory via NFS by adding following line to /etc/exports:

/mnt/disk3/nfsshare/    windowsvm(rw,all_squash,anonuid=65534,anongid=65534)

It will map all the userd ids to our nobody user.

To make the change active, it should be followed by either restarting of the NFS daemon or by executing 'exportfs -ra'.

Mounting the NFS Volume in Windows

Start the Windows VM. To add support for NFS, we must go to "Turn Windows Features on or off" and enable "Services for NFS".

After that we need to use the same anonymous UID and GID as set on server side. For that open regedit, find "Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\ClientForNFS\CurrentVersion\Default" and add there two DWORD values named AnonymousUid and AnonymousGid with the same value as used in /etc/exports.

With the IP address of the Linux host set to we can now execute the following command in command.com shell: 

mount -o anon \\\mnt\diskx\nfsshare Z:

After that the NFS volume should appear as Windows volume "Z:" and we should be able both to read and write to it.

Save the command to a file called mounntfs.bat and keep it for future use. I have it in my home folder in sub-folder named scripts.

Mounting It Automatically on Startup

To avoid the necessity to execute the script manually each time you start the VM, you can use Windows Scheduler and create and a scheduled task for that. The desired task in my cas uses SYSTEM account, triggers at startup whenever a network connection is available.