Building Maven release for two or more OS

Maintaining product with a long history is not an easy work. And definitely we have really long history. In the glorious time of concurring the world of software industry, when programming was the job of the toughest man and women, our company had an impressing portfolio of supported operating systems. Browsing the sources of native components, I found the following comment in the Makefile

Impressing list, but the only one which is supported 25 years later is Linux. And also we added MS Windows. And… we switched to little bit more recent technologies.

After all this years only very small pieces of native code was left, the core product was rewritten in Java, but we still had a dozen C files. And the binaries produced by this files was consumed by product build as artifact and everything was great until the day when small change was introduced. And I found that I can’t produce release version of binaries, but only snapshot. The reason — same code was built on 2 Jenkins nodes on different OS and it was producing artifacts with same name and groupId, but different classifier. At my first glance this should not be a problem, but the artifactory where the releases was published was not on the same opinion. Oh, the overwrite of releases is forbidden and we are publishing pom files. So all release plugins was kicked off the list of possible solutions. Another constraint — we have pretty old Jenkins, which can’t be upgraded at this moment, because we are using some corporate plugins and here is the place when I’m recalling the Catch 22 — you can’t upgrade Jenkins because you have outdated plugins, so you can’t update plugins, because you have outdated Jenkins. So forget about Jenkins plugins.

Damn. Looks like dead end.

You can split the project into Windows and Linux one. But then you will need to maintain two repos.

Well, what is the release procedure? In short — you are checking you are not using snapshot dependencies, you are tagging the code in the SCM and than you are building the artifacts from this tag. So how to workaround this? I didn’t found smarter way than tag the SCM with temporary tag, build the snapshot, if the build is successful rename temporary tag to the release one and than publish the artefacts. But we can’t install Jenkins plugins, remember? So here is how it happens.

Checkout and tag:

stage (‘Tag’){
node (“centos-7-x64”) {
stage (‘Checkout’) {
cleanWs()
checkout([$class: ‘GitSCM’,
branches: [[name: ‘*/master’]],
userRemoteConfigs: [[url: ‘https://gitserver/project.git', credentialsId: ‘3d4814fe-bdf1–4e08–909a-cda9d0557461’]]
])
withCredentials([usernamePassword (credentialsId: ‘3d4814fe-bdf1–4e08–909a-cda9d0557461’, usernameVariable: ‘GIT_USERNAME’, passwordVariable: ‘GIT_PASSWORD’)]) {
sh ‘’’if [ ! -d .git ]; then mkdir .git; fi
git config — local http.sslVerify “false”
git config — local credential.username builder
git config — local credential.helper “!f() { echo username=\\$GIT_USERNAME; echo password=\\$GIT_PASSWORD; }; f”
git tag ${GITTAG}-op
git push origin ${GITTAG}-op
git config — local — remove-section credential’’’
}
}
}
}

Why?

  • Jenkins git plugin does not work out fine with tags.
  • You should create .git directory before repository checkout if you want to save local settings.
  • We don’t use ssh key, because we don’t want permanent credential storage in “plaintext”

Next step build on Linux and Windows:

stage('Build') {
parallel (
'Linux': {
node ("centos") {
cleanWs()
checkout([$class: 'GitSCM',
branches: [[name: 'refs/tags/${GITTAG}-op']],
userRemoteConfigs: [[url: 'https://gitserver/project.git', credentialsId: '3d4814fe-bdf1-4e08-909a-cda9d0557461']]
])
configFileProvider([configFile(fileId: 'org.jenkinsci.plugins.configfiles.maven.GlobalMavenSettingsConfig1461312644881', variable: 'MAVEN_SETTINGS_XML')]) {
sh '''mvn --batch-mode -s $MAVEN_SETTINGS_XML versions:set -DnewVersion=${GITTAG}-SNAPSHOT -DprocessAllModules -DgenerateBackupPoms=false
mvn --batch-mode -s $MAVEN_SETTINGS_XML -U -X clean deploy'''
}
}
},

'Windows': {
node ("windows") {
cleanWs()
checkout([$class: 'GitSCM',
branches: [[name: 'refs/tags/${GITTAG}-op']],
userRemoteConfigs: [[url: 'https://gitserver/project.git', credentialsId: '3d4814fe-bdf1-4e08-909a-cda9d0557461']]
])
configFileProvider([configFile(fileId: 'org.jenkinsci.plugins.configfiles.maven.GlobalMavenSettingsConfig1461312644881', variable: 'MAVEN_SETTINGS_XML')]) {

sh '''mvn --batch-mode -s $MAVEN_SETTINGS_XML versions:set -DnewVersion=${GITTAG}-SNAPSHOT -DprocessAllModules -DgenerateBackupPoms=false
mvn --batch-mode -s $MAVEN_SETTINGS_XML -U -X clean deploy'''
}

}
}
)
}

What happens here?

Linux build is always faster than Windows one. We have same codebase written in C but slightly different build profiles which are distinguished by OS profile in pom.xml:

<profiles>
<profile>
<id>windows</id>
<activation>
<activeByDefault>true</activeByDefault>
<os>
<family>Windows</family>
</os>
</activation>
<build>....

And here we have splendid legacy structure — all product history in one build procedure — maven is executing ant, ant is executing some ancient shell scripts, shell scripts are executing some configurations steps and than make, and finally make is executing compiler and linker and so on.

Both builds are producing artifacts with same group and artifact id, but different descriptors. The version of artifacts is ${GITTAG}-SNAPSHOT

Even if each artifact has unique descriptor, project pom files are uploaded to repository, and overwrite in release repository is forbidden, so first we should publish them as snapshot.

And final step — the ugly hack to publish the artifacts.

stage('Release') {
node ("centos") {
cleanWs()
checkout([$class: 'GitSCM',
branches: [[name: 'refs/tags/${GITTAG}-op']],
userRemoteConfigs: [[url: 'https://gitserver/project.git', credentialsId: '3d4814fe-bdf1-4e08-909a-cda9d0557461']]
])
configFileProvider([configFile(fileId: 'org.jenkinsci.plugins.configfiles.maven.GlobalMavenSettingsConfig1461312644881', variable: 'MAVEN_SETTINGS_XML')]) {

sh '''cd release-project
mvn --batch-mode -s $MAVEN_SETTINGS_XML -Drel.version=${GITTAG}-SNAPSHOT clean install -U -X -Drelease.number=${GITTAG}-SNAPSHOT
mvn -B -s $MAVEN_SETTINGS_XML -X -e deploy:deploy-file -DartifactId=artifact_name \
-Dversion=${GITTAG} \
-Dpackaging=zip \
-Dfile=target/artifacts/artifact-linux-x86.zip \
-Dfiles=target/artifacts/native-utils-windows.zip,target/artifacts/native-utils-other.zip,target/artifacts/native-utils-another.zip \
-Dtypes=zip,zip,zip \
-Dclassifier=linux-x86 \
-Dclassifiers=windows,other,another \
-DrepositoryId=repo-delivery-releases \
-DgroupId=org.company.native \
-Durl=http://repository/content/repositories/org.company-release/'''
withCredentials([usernamePassword (credentialsId: '3d4814fe-bdf1-4e08-909a-cda9d0557461', usernameVariable: 'GIT_USERNAME', passwordVariable: 'GIT_PASSWORD')]) {sh '''git config --local http.sslVerify "false"
git config --local credential.username builder
git config --local credential.helper "!f() { echo username=\\$GIT_USERNAME; echo password=\\$GIT_PASSWORD; }; f"
git tag ${GITTAG} ${GITTAG}-op
git tag -d ${GITTAG}-op
git push origin ${GITTAG} :${GITTAG}-op
echo "Done"
'''
}
}
}
}

What we have here?

First we checkout the sources tagged with ${GITTAG}-op

Than we build release project which is only downloading all needed artifacts with version ${GITTAG}-SNAPSHOT

Than we deploy this artifacts as release version and finally we rename tag from ${GITTAG}-op to ${GITTAG}

Why this way?

A long time ago our product was available on 5 different OS platforms. managing 5 different repositories with native code for product, which is 99% written in Java was not a good idea. And the changes in the native code was really rare. So we have just one version of native artifacts and everybody is afraid of touching it.

Why this is not a good idea?

Mostly because it is ugly hack and it is stopping you from making any optimisations — it is working, so don’t touch it. In my opinion we may have same codebase, but two different maven projects for two different OS. This is a good beginning of a long way to the bight future i.e. get rid of Cygwin. I had thousands of reasons to do it, but no blessing from the management.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store