Building a commandline interface with boost::program_options

My ONTF project “DomBackUp” can be configured and run via the command-line without having the configuration database installed on the Domino server.
DomBackUp is a fork of nKBackup by Tom Lyne et al. I found the command-line interface code a bit hard to maintain when it comes to enhancements.
So I was looking for an alternative way to create the command-line interface in C++ platform-independent.

boost::program_options is a library that makes it easy to parse command-line options, for example, for console applications.

Here is the code snippet from the DomBackUp project

try {
#include<boost/program_options/options_description.hpp>
#include<boost/program_options/parsers.hpp>
#include<boost/program_options/variables_map.hpp>

...

namespace po = boost::program_options;

...


po::options_description description("DomBackUp usage",PO_LINE_LENGTH);
description.add_options()
	("help,h", "Display this help message")

	("source,s", po::value( &szSource ), "source file/folder")

	("dest,d", po::value( &szDestination ), "destination file/folder")

	("include-sub-dirs,inc", po::bool_switch( &bIncludeSubDirs )->default_value(false), 
	"Include subdirectories, applies to folder backup only (optional, default = false)")

	("throttle,t", po::bool_switch( &bThrottle )->default_value(false), 
	"Wait short time between file writes (optional, default = false)")

	("zip,z", po::bool_switch( &bZipAfterBackup )->default_value(false),
	"Move file to .zip archive after backup (optional, default = false)")

	("unique-filename,u", po::bool_switch( &bUniqueFileName )->default_value(false),
	"appends a unix timestamp to the archive filename (optional, default = false)")

	("application-type,a", po::value( &szAppType )->default_value("nsf"),
	"nsf,ntf,both = application type (optional, default = nsf only)")

	("input-file,f", po::value( &szInputFile ), 
	"Backup all the files specified in an .ind file created in the Domino data folder to <dest>")

	("version,v", "Display the version number");

po::variables_map vm;
po::store(po::command_line_parser(argc, argv).options(description).run(), vm);
po::notify(vm);

if(vm.count("help")) {
	std::cout << description << "\n";
	return FALSE;
}

if(vm.count("version")) {
	AddInLogMessageText("V %s on %s", NOERROR, szVersionNumber.c_str(), szServer);
	AddInLogMessageText("Termination complete.\n", NOERROR);
	return FALSE;
}
}

catch(po::error& e) { 
AddInLogMessageText((char*)e.what(), NOERROR);
return (ERR(error));
}

First of all, you have to include some header files and (optional) set the namespace to be used. The only downside of using boost::program_options is the fact that you have to build the static .lib. While most parts of the boost libraries are header only, boost::program_options is not. But once you have the library at hand, it is easy to write the code.

The first line of code creates your interface and sets the Title as well as the line-length. The line-length is 80 by default.

po::options_description description("DomBackUp usage",PO_LINE_LENGTH);

The next thing to do is to add all the options you like to control via the command-line

description.add_options()
	("help,h", "Display this help message")

	("source,s", po::value( &szSource ), "source file/folder")

Each option can have a description and the command, both verbose (–help) and abbreviated (-h).
In addition, you can bind the command to a variable. The variable can be of any type, string, int or boolean.

For boolean variables, you can implement some kind of toggle mechanism.

description.add_options()
	("zip,z", po::bool_switch( &bZipAfterBackup )->default_value(false),
	"Move file to .zip archive after backup (optional, default = false)")

In this example, &bZipAfterBackup by default is set to false. To enable the feature, simply add –zip or -z to the command-line. This will toggle from false tu true.

The –help (-h) command does not have any value binding. So we have to add a parser and some ther lines of code that are processed, when the –help (-h) command is being used.

	
        po::variables_map vm;
	po::store(po::command_line_parser(argc, argv).options(description).run(), vm);
	po::notify(vm);

	if(vm.count("help")) {
		std::cout << "\n" << description << "\n";
		return FALSE;
	}

This is what you get when you type “load nbackup -h”

commandlineCool, isn’t it?. Just a couple of lines of code and you get a professional command-line interface.

As said before, the downside is the overhead of having the boost libraries included in your project.

But the footprint is minimal, although the boost libraries are many GB of code.


Building ONTF DomBackUp with TeamCity

Building binaries for multiple platforms from source is time consuming. For my ONTF project “DomBackUp” I have to build binaries for AIX (not sure, if I can support AIX in future builds ) , LINUX and Windows, both on 32Bit and 64Bit.

Aside from Atlassian JIRA and STASH, a couple of virtual machines are involved. I also started using TEAMCITY to automatically build the binaries without going to each of the build systems and invoke the build process manually.

TeamCity is a Java-based CI server package. The TeamCity installation and configuration is quick and easy. The fact that it is Java-based should not be an impediment to Windows development shops.
The TeamCity server is a main component, but the browser-hosted interface serves as the primary way to administer TeamCity users, agents, projects, and build configurations.

The main advantages are

  • Easy to setup, use, and configure
  • Widely-used and well documented
  • Integration with a wide variety of tools and technologies
  • Professional Server is free for up to 3 agents and 20 build configurations.

teamcitybuild

You can create the build steps manually or let TeamCity search your GIT repository for existing .sln files and propose build steps automatically. All you have to do is to review the steps and select the correct build configuration ( x64 or Win32 ). For the Linux builds, I use boost build scripts. So you only have to tell TeamCity to invoke the script when the build agent runs.

TeamCity will automatically grab the output from the build script. This makes it very easy to identify errors in the script itself or even compile errors.

teamcitybuild1

teamcitybuild2

Using a CI solution makes it very easy to create nightly builds. Due to the flexibility in configuration, you are able to create the binaries aside from any needed template, have CI update and harmonize the version and build numbers across all parts of your project, create release notes from JIRA and put all parts together to build a shippable package.

The process can be triggered manually or scheduled to create nightly builds.

You are also able to rebuild any previous version by simply checkout the correct commit from your repository.